How do I implement a copy constructor for a class that has a unique_ptr
member variable? I am only considering C++11.
6 Answers
Since the unique_ptr
can not be shared, you need to either deep-copy its content or convert the unique_ptr
to a shared_ptr
.
class A
{
std::unique_ptr< int > up_;
public:
A( int i ) : up_( new int( i ) ) {}
A( const A& a ) : up_( new int( *a.up_ ) ) {}
};
int main()
{
A a( 42 );
A b = a;
}
You can, as NPE mentioned, use a move-ctor instead of a copy-ctor but that would result in different semantics of your class. A move-ctor would need to make the member as moveable explicitly via std::move
:
A( A&& a ) : up_( std::move( a.up_ ) ) {}
Having a complete set of the necessary operators also leads to
A& operator=( const A& a )
{
up_.reset( new int( *a.up_ ) );
return *this,
}
A& operator=( A&& a )
{
up_ = std::move( a.up_ );
return *this,
}
If you want to use your class in a std::vector
, you basically have to decide if the vector shall be the unique owner of an object, in which case it would be sufficient to make the class moveable, but not copyable. If you leave out the copy-ctor and copy-assignment, the compiler will guide your way on how to use a std::vector with move-only types.
The usual case for one to have a unique_ptr
in a class is to be able to use inheritance (otherwise a plain object would often do as well, see RAII). For this case, there is no appropriate answer in this thread up to now.
So, here is the starting point:
struct Base
{
//some stuff
};
struct Derived : public Base
{
//some stuff
};
struct Foo
{
std::unique_ptr<Base> ptr; //points to Derived or some other derived class
};
... and the goal is, as said, to make Foo
copiable.
For this, one needs to do a deep copy of the contained pointer to ensure the derived class is copied correctly.
This can be accomplished by adding the following code:
struct Base
{
//some stuff
auto clone() const { return std::unique_ptr<Base>(clone_impl()); }
protected:
virtual Base* clone_impl() const = 0;
};
struct Derived : public Base
{
//some stuff
protected:
virtual Derived* clone_impl() const override { return new Derived(*this); };
};
struct Foo
{
std::unique_ptr<Base> ptr; //points to Derived or some other derived class
//rule of five
~Foo() = default;
Foo(Foo const& other) : ptr(other.ptr->clone()) {}
Foo(Foo && other) = default;
Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; }
Foo& operator=(Foo && other) = default;
};
There are basically two things going on here:
The first is the addition of copy and move constructors, which are implicitly deleted in
Foo
as the copy constructor ofunique_ptr
is deleted. The move constructor can be added simply by= default
... which is just to let the compiler know that the usual move constructor shall not be deleted (this works, asunique_ptr
already has a move constructor which can be used in this case).For the copy constructor of
Foo
, there is no similar mechanism as there is no copy constructor ofunique_ptr
. So, one has to construct a newunique_ptr
, fill it with a copy of the original pointee, and use it as member of the copied class.In case inheritance is involved, the copy of the original pointee must be done carefully. The reason is that doing a simple copy via
std::unique_ptr<Base>(*ptr)
in the code above would result in slicing, i.e., only the base component of the object gets copied, while the derived part is missing.To avoid this, the copy has to be done via the clone-pattern. The idea is to do the copy through a virtual function
clone_impl()
which returns aBase*
in the base class. In the derived class, however, it is extended via covariance to return aDerived*
, and this pointer points to a newly created copy of the derived class. The base class can then access this new object via the base class pointerBase*
, wrap it into aunique_ptr
, and return it via the actualclone()
function which is called from the outside.
Try this helper to create deep copies, and cope when the source unique_ptr is null.
template< class T >
std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
{
return source ? std::make_unique<T>(*source) : nullptr;
}
Eg:
class My
{
My( const My& rhs )
: member( copy_unique(rhs.member) )
{
}
// ... other methods
private:
std::unique_ptr<SomeType> member;
};
Daniel Frey mention about copy solution,I would talk about how to move the unique_ptr
#include <memory>
class A
{
public:
A() : a_(new int(33)) {}
A(A &&data) : a_(std::move(data.a_))
{
}
A& operator=(A &&data)
{
a_ = std::move(data.a_);
return *this;
}
private:
std::unique_ptr<int> a_;
};
They are called move constructor and move assignment
you could use them like this
int main()
{
A a;
A b(std::move(a)); //this will call move constructor, transfer the resource of a to b
A c;
a = std::move(c); //this will call move assignment, transfer the resource of c to a
}
You need to wrap a and c by std::move because they have a name std::move is telling the compiler to transform the value to rvalue reference whatever the parameters are In technical sense, std::move is analogy to something like "std::rvalue"
After moving, the resource of the unique_ptr is transfer to another unique_ptr
There are many topics that document rvalue reference; this is a pretty easy one to begin with.
Edit :
The moved object shall remain valid but unspecified state.
C++ primer 5, ch13 also give a very good explanation about how to "move" the object
unique_ptr
is not copyable, it is only moveable.
This will directly affect Test, which is, in your second, example also only moveable and not copyable.
In fact, it is good that you use unique_ptr
which protects you from a big mistake.
For example, the main issue with your first code is that the pointer is never deleted which is really, really bad. Say, you would fix this by:
class Test
{
int* ptr; // writing this in one line is meh, not sure if even standard C++
Test() : ptr(new int(10)) {}
~Test() {delete ptr;}
};
int main()
{
Test o;
Test t = o;
}
This is also bad. What happens, if you copy Test
? There will be two classes that have a pointer that points to the same address.
When one Test
is destroyed, it will also destroy the pointer. When your second Test
is destroyed, it will try to remove the memory behind the pointer, as well. But it has already been deleted and we will get some bad memory access runtime error (or undefined behavior if we are unlucky).
So, the right way is to either implement copy constructor and copy assignment operator, so that the behavior is clear and we can create a copy.
unique_ptr
is way ahead of us here. It has the semantic meaning: "I am unique
, so you cannot just copy me." So, it prevents us from the mistake of now implementing the operators at hand.
You can define copy constructor and copy assignment operator for special behavior and your code will work. But you are, rightfully so (!), forced to do that.
The moral of the story: always use unique_ptr
in these kind of situations.
std::vector
. – codefxunique_ptr
, you probably want a move constructor, if your goal is to put the data in astd::vector
. On the other hand, the C++11 standard has automatically created move constructors, so maybe you do want a copy constructor... – Yakk - Adam Nevraumont