L. Kue
L. Kue

Reputation: 473

Compact way to make a Variable non-copyable

I have a couple classes in my code, of which I need to copy objects.

However, some of those classes use data which I would like to skip when copying, like owner pointers.

So far, the only way I've found of doing this either to avoid copying entirely and construct a completely new object by hand every time I need to copy one, or wrapping the owner pointer in a private, non-copyable struct, like this:

class MyClass {
    // non-copyable owner
    struct Owner {
        Owner() = default;
        ~Owner() = default;

        Owner(const Owner& o) = delete;
        Owner& operator=(const Owner& o) = delete;

        SomeOtherClass* pointer = nullptr;
    };

    Owner owner = Owner();
}

However, doing it this way seems a little verbose.

(Note I would not like to use std::unique_ptr as I do not wish to deconstruct the owner when the object is deconstructed)

Is there a more compact / efficient / readable way of doing this?

Edit: the invalidating of default marked constructors etc. stemmed from me not initializing owner with a default value. Silly.

EDIT 2: Perhaps I should clear some things up: The owner pointer should point to the object that owns the MyClass, as a way for the owned object to refer to its owner. Not the other way around. That is why I wish to not copy the pointer, as an object with a different owner that is supposed to copy this object should not change which object it is owned by.

The accepted answer enables me to do just that. I hope this cleared up some of the confusion.

Upvotes: 1

Views: 168

Answers (4)

JVApen
JVApen

Reputation: 11317

An owner that does not own the resource? Sounds like you are lying in your code, if I would see this in code, I would think you have a bug.

That said, unique_ptr is really the best way of dealing with it. You could give it a custom destructor to do the closing of the resource (like closing a file handle). From a readable perspective, this will be the easiest to understand instead of having your custom class.

I don't know how you define efficient, however, assuming runtime efficiency, their ain't any effect.

The most compact way of writing will be inheriting from a NonCopyable class, boost has one, however it is very easy to write if you don't want that dependency.

From the readable perspective, I actually would go with a variation of your orginal implementation, which is: follow the rule of 5: specify Ctor, copy Ctor, move ctor, copy assign and move assign in your class.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

Copy (and move) have required semantics due to how elision works, and to a lesser degree how containers work.

Well behaved value types shouldn't behave surprisingly differently if a copy or move is elided. They should also behave well when stored in a std::vector.

Your description seems a lot like how auto_ptr was hacked into being a unique_ptr before we had language support for moving. It was a clever idea that turned into a monster of a bad one, and auto_ptr is one of the few deprecated features of the standard library because of how much of a bad idea it turned out to be.

Split the "state" of your types from its identity. Ownership is an identity feature, for example, while height is a state feature.

Store the state in a struct within your type.

The identity either store within your type, or in another substruct. The identity substruct should have =delete copy/move operations.

Provide a way to create a new object with a new identity but the same state. If the object type is Foo, and its state is FooState, then you'd have a Foo(FooState) explicit constructor.

Have a method that produces a state-wise copy of a Foo.

Do not permit Foo to be copied. The semantics of copying a Foo where the identity changes (is cleared) after a copy are not healthy copy semantics. A Foo should be non-copyable.

Move semantics are possible; in such a case, you'd report to your owner(s) that your location is changing (through some interface) -- like:

if (owner)
  owner->child_moving( &old, this );
for( auto* child:children )
  child->parent_moving( &old, this );

which permits parent/children to update their owner/child pointers.

Unless you do that kind of thing, you don't want to pretend to have value semantics; delete your copy/move operations instead of implementing insane ones.

Upvotes: 2

huseyin tugrul buyukisik
huseyin tugrul buyukisik

Reputation: 11910

Maybe something like a factory method at least evades the copying part in compile time?

// a method of MyClass
Owner * MyOwner()
{
     // check if created
     if(uidMap.find(parentObjectUid )==uidMap.end())
     {
         // create if needed
         uidMap.insert(std::pair<size_t,Owner *>(parentObjectUid ,new Owner()));
         // you can even use shared/unique ptr instead of new Owner()
         //    if you want automatic garbage
         return uidMap[parentObjectUid ];

     }
     else
     {
          return uidMap[parentObjectUid];
     }
}

then creating uid may need an unwanted singletone or similar global synchronized production. So as long as MyClass is copied with same uid, both copies will have same single object of Owner.

MyClass a;
MyClass b;
Owner * o = a.MyOwner(); // => creates
Owner * p = a.MyOwner(); // => uses
Owner * q = b.MyOwner(); // => creates another
a = b; // doesn't copy anything Owner

a unique id could be an incremented 64bit integer.

Upvotes: 1

Not a real meerkat
Not a real meerkat

Reputation: 5729

You can use a std::unique_ptr with a deleter that does nothing.

template<typename T>
struct nop_deleter<T> {void operator()(T*){};};

class MyClass {
  std::unique_ptr<SomeType, nop_deleter<SomeType>> owner;
};

The "owned" object will then not be deconstructed. (At this point it's not really owned anymore, but that's just me being pedantic).

If you want it to be a tad more readable you can always alias the type:

template<typename T>
struct nop_deleter<T> {void operator()(T*){};};

template<typename T>
using Owner = std::unique_ptr<T, nop_deleter<T>>;

class MyClass {
  Owner<SomeType> owner;
};

Upvotes: 3

Related Questions