std::unique_ptr
has a deleted copy constructor, which means that if you have a unique_ptr
in your class Foo
as a data member then you must write your own copy constructor for Foo
and manually deep-copy that member (even if the compiler-generated copy constructor would be fine for all other members).
In order to be able to copy in a polymorphic way, the clone()
method pattern can be used. Let's assume our objects have a clone method like this:
class Base {
virtual std::unique_ptr<Base> clone() = 0;
};
Foo looks like this now:
class Foo {
public:
...
Foo(Foo const& other)
: b(other.b->clone())
, // init 10 more members that could otherwise be auto-copied just fine
// with the automatically generated copy constructor
{}
...
private:
std::unique_ptr<Base> b;
//10 more data members
};
Now, I found a way to auto-clone Foo::b
, by writing a wrapper over unique_ptr
that defines the copy constructor and assignment by calling clone
.
template <typename T>
class auto_cloned_unique_ptr
{
private:
std::unique_ptr<T> up;
public:
// copy constructor
auto_cloned_unique_ptr(auto_cloned_unique_ptr<T> const& other)
: up(other.up->clone()) {}
// copy assignment
auto_cloned_unique_ptr<T>& operator =(auto_cloned_unique_ptr<T> const& other)
{
this->up = other.up->clone();
return *this;
}
auto_cloned_unique_ptr(std::unique_ptr<T> _up)
: up(std::move(_up)) {}
// Delegate everything else to unique_ptr
auto_cloned_unique_ptr(auto_cloned_unique_ptr<T>&& other)
: up(std::move(other.up)) {}
auto_cloned_unique_ptr<T>& operator =(auto_cloned_unique_ptr<T>&& other)
{
this->up = std::move(other.up);
return *this;
}
auto operator *() const {return *up;}
auto operator->() const {return up.operator->();}
auto get() -> const {return up.get();}
};
Now if we use this we don't need to define our own copy constructor:
class Foo2 {
public:
...
private:
auto_cloned_unique_ptr<Base> b;
//10 more data members
};
Is such an approach very much frowned upon (for using a non-standard wrapper over unique_ptr
)?
Let me first paraphrase what you want to do: You want that each instance of Foo
has its own instance of Base
in b
; in particular, if you copy a Foo
, the copy will have its own new Base
, initially with the same "value". In other words, Base
should behave like a value.
At the same time, you can't store Base
directly in Foo
because it is an abstract class. In other words, you want b
to be polymorphic
.
There you have it: you want a polymorphic value. Other people have recognized this need and proposed for C++20 as polymorphic_value<Base>
. From the documentation:
The class template, polymorphic_value, confers value-like semantics on a free-store allocated object. A polymorphic_value may hold an object of a class publicly derived from T, and copying the polymorphic_value will copy the object of the derived type.
It has a reference implementation that you can use as of now. Very simply put, it is an wrapper around std::unique_ptr
similar to what you propose.
The problem with your approach is that it is changing the meaning of a unique_ptr. The key thing about a unique_ptr is that it tells who is the owner of an object. If you add a copy constructor for unique_ptr, what does that mean? Are you copying the ownership? A and B both uniquely own the thing? That does not make sense. If they share ownership, then you should be using the shared_ptr to indicate the shared ownership. If you want to have a unique owner of a copy of the object, you would naturally indicate that by make_unique(*pFoo). With base and derived objects, having the base object have a
virtual unique_ptr<Foo> Clone() const=0;
is a perfectly normal construct. That is, the derived classes know how to copy themselves so they don't produce a sliced copy, but they return a unique_ptr to the base class to indicate that you will own the copy they have produced. Within these clone operations, yes, you will need to explicitly handle non-copyable members of the derived classes, so you won't be able to just use a default or generated copy constructor. You need to answer "what does it mean to copy something that contains this thing that can't be copied?"
As a concrete example, what would it mean to copy a derived class that had a mutex? What if it were locked and another thread were waiting on it? See why it's hard to give a general answer?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With