Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy Constructor to transfer ownership of a unique_ptr

I need to write a copy constructor that also transfer the ownership of a unique_ptr member of the object being copied. The situation is as follows:

class C{
   // C class stuff       
};

class A{
public:
    public A();
    public A(const A& a);
private:
    std::unique_ptr<C> c_;
}

class B{

public:

    B(const A& b) : a_(a){}

private:
     A a_;


};

How should I implement the copy constructor for A?

like image 342
gcswoosh Avatar asked Mar 18 '15 14:03

gcswoosh


3 Answers

Your intent or approach is wrong, I guess.

The copy-constructor is meant to create a copy of the argument, but since unique_ptr maintains sole ownership one cannot make a copy of it. You could in fact make the unique_ptr member mutable and then move the resource it points to in the copy-constructor but that would be absolutely insane (this is what std::auto_ptr does and this is why it's deprecated).

Therefore either you need to:

  • add a move-constructor to A and B (+ make copy-constructor deleted)
  • switch from unique_ptr to shared_ptr, but only if C is actually meant to be shared

There's also a third option, which is to make a copy of the object pointed to by the unique_ptr in A's copy-constructor, i.e.:

A::A(const A& a) : c_(std::unique_ptr<C>(a.c_ ? new C(*a.c_) : nullptr)) {
}
like image 122
Adam Romanek Avatar answered Sep 19 '22 02:09

Adam Romanek


Obviously you cannot just do an assignment of std::unique_ptrs as their assignment operator is deleted. This is intentional to force the programmer to define the behavior he wants.

  1. The new item takes ownership of c_, invalidating the original item.
  2. The new item makes a copy of c_, retaining the original items validity.
  3. The new item shares ownership of c_ so that both the new and original items reference the same object.

In case 1 what you're looking for is a move constructor, and the default move constructor will work fine. So you don't need to write any code, you can just do:

A temp;
A foo(std::move(temp));

Note that temp is invalid after it is moved.

In case 2 you'll need to add a custom copy constructor to A to create a copy of the original's c_:

A(const A& a):c_(new C(*(a.c_))){}

After defining this in A you can do:

A foo(A());

Note that this depends upon C's copy constructor being functional.

In case 3 you'll need to fundamentally change A from using a std::unique_ptr to using a std::shared_ptr, so the definition of c_ would become:

std::shared_ptr<C> c_;

Your construction of c_ would be identical to what you're already using for the std::unique_ptr version of c_. So just using the default implementations you could do:

A foo;
A bar(foo);

And now foo and bar point to the same C object, and share ownership of it. This shared object will not be deleted until all shared_ptrs referencing it have been deleted.

like image 23
Jonathan Mee Avatar answered Sep 18 '22 02:09

Jonathan Mee


Technically, to

write a copy constructor that also transfer the ownership of a unique_ptr member of the object being copied

you can just do this:

class Bad
{
private:
    unique_ptr<int> p_;
public:
    Bad(): p_( new int(666) ) {}
    Bad( Bad& other )
       : p_( move( other.p_ ) )
    {}
};

Because a copy constructor can have also this signature, plus two more, in addition to the more conventional Bad( const Bad& ).

I named that class Bad because it's really bad, it just does not make sense to do this thing except as sabotage of someone else's code.

Instead of a copy constructor that doesn't copy,

  • implement a move constructor that moves, or

  • implement an ordinary copy constructor that copies, or

  • change the class design to e.g. shared ownership.

like image 41
Cheers and hth. - Alf Avatar answered Sep 19 '22 02:09

Cheers and hth. - Alf