Как реализовать конструктор копирования для класса с переменной unique_ptr
? Я рассматриваю только С++ 11.
Копировать конструктор для класса с unique_ptr
Ответ 1
Поскольку unique_ptr
не может использоваться совместно, вам необходимо либо глубоко скопировать его содержимое, либо преобразовать unique_ptr
в 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;
}
Вы можете, как упоминалось в NPE, использовать move-ctor вместо copy-ctor, но это приведет к различной семантике вашего класса. Перемещение-ctor должно было бы сделать элемент перемещаемым явно через std::move
:
A( A&& a ) : up_( std::move( a.up_ ) ) {}
Наличие полного набора необходимых операторов также приводит к
A& operator=( const A& a )
{
up_.reset( new int( *a.up_ ) );
return *this,
}
A& operator=( A&& a )
{
up_ = std::move( a.up_ );
return *this,
}
Если вы хотите использовать свой класс в std::vector
, вам в основном нужно решить, должен ли вектор быть единственным владельцем объекта, и в этом случае было бы достаточно сделать класс подвижным, но не скопированным. Если вы не укажете copy-ctor и copy-присваивание, компилятор будет определять способ использования std::vector с типами только для перемещения.
Ответ 2
Обычный случай для того, чтобы иметь unique_ptr
в классе, - это иметь возможность использовать наследование (в противном случае обычный объект также часто использовал бы это, см. RAII). Для этого случая до сих пор нет подходящего ответа в этой теме.
Итак, вот отправная точка:
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
};
... и цель, как уже было сказано, сделать Foo
копируемым.
Для этого нужно сделать глубокую копию содержимого указателя, чтобы убедиться, что производный класс скопирован правильно.
Это может быть достигнуто путем добавления следующего кода:
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, but a user-defined dtor is not necessary due to unique_ptr
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;
};
Здесь в основном происходит две вещи:
-
Первый - это добавление конструкторов копирования и перемещения, которые неявно удаляются в
Foo
при удалении конструктора копированияunique_ptr
. Конструктор перемещения может быть добавлен просто с помощью= default
..., чтобы дать компилятору понять, что обычный конструктор перемещения не должен быть удален (это работает, посколькуunique_ptr
уже имеет конструктор перемещения, который можно использовать в этом случае).Для конструктора копирования
Foo
аналогичного механизма не существует, так как нет конструктора копированияunique_ptr
. Таким образом, нужно создать новыйunique_ptr
, заполнить его копией оригинального pointee и использовать в качестве члена скопированного класса. -
В случае наследования, копия оригинального pointee должна быть сделана тщательно. Причина в том, что выполнение простого копирования с помощью
std::unique_ptr<Base>(*ptr)
в приведенном выше коде приведет к нарезке, т.е. Копируется только базовый компонент объекта, а производная часть отсутствует.Чтобы избежать этого, копия должна быть сделана через шаблон клона. Идея состоит в том, чтобы сделать копию через виртуальную функцию
clone_impl()
которая возвращаетBase*
в базовом классе. Однако в производном классе он расширяется посредством ковариации для возвратаDerived*
, и этот указатель указывает на вновь созданную копию производного класса. Затем базовый класс может получить доступ к этому новому объекту через указатель базового классаBase*
, обернуть его вunique_ptr
и вернуть его через фактическую функциюclone()
которая вызывается извне.
Ответ 3
Попробуйте этот помощник для создания глубоких копий и справитесь, когда source unique_ptr имеет значение null.
template< class T >
std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
{
return source ? std::make_unique<T>(*source) : nullptr;
}
Например:
class My
{
My( const My& rhs )
: member( copy_unique(rhs.member) )
{
}
// ... other methods
private:
std::unique_ptr<SomeType> member;
};
Ответ 4
Даниэль Фрай упомянул о решении для копирования, я хотел бы поговорить о том, как переместить 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_;
};
Они называются конструктором перемещения и назначением перемещения
вы могли бы использовать их вот так
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
}
Вам нужно обернуть a и c с помощью std:: move, потому что у них есть имя std:: move сообщает компилятору преобразовать значение в rvalue, независимо от параметров В техническом смысле std:: move аналогичен "std:: rvalue"
После перемещения ресурс unique_ptr передается другому уникальному_ptr
Есть много тем, которые документируют ссылку rvalue; это довольно легко начать с.
Изменить:
Перемещенный объект должен оставаться действительным, но неопределенным состоянием.
С++ primer 5, ch13 также дает очень хорошее объяснение того, как "перемещать" объект
Ответ 5
Я предлагаю использовать make_unique
class A
{
std::unique_ptr< int > up_;
public:
A( int i ) : up_(std::make_unique<int>(i)) {}
A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};
int main()
{
A a( 42 );
A b = a;
}
Ответ 6
unique_ptr
не копируемый, это только подвижно.
Это напрямую повлияет на Test, который во втором примере также может быть перемещаемым и не копируемым.
На самом деле, это хорошо, что вы используете unique_ptr
который защищает вас от большой ошибки.
Например, основная проблема с вашим первым кодом заключается в том, что указатель никогда не удаляется, что очень, очень плохо. Скажем, вы бы это исправили:
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;
}
Это тоже плохо. Что произойдет, если вы скопируете Test
? Будет два класса, у которых есть указатель, указывающий на один и тот же адрес.
Когда один Test
уничтожен, он также уничтожит указатель. Когда ваш второй Test
будет уничтожен, он также попытается удалить память за указателем. Но он уже был удален, и мы получим некоторую ошибку во время выполнения доступа к памяти (или неопределенное поведение, если нам не повезет).
Таким образом, правильный путь - реализовать конструктор копирования и оператор присваивания копии, чтобы поведение было понятным, и мы могли создать копию.
unique_ptr
далеко впереди нас здесь. Это имеет смысловой смысл: " Я unique
, поэтому вы не можете просто скопировать меня". Таким образом, это предотвращает нас от ошибки при реализации операторов под рукой.
Вы можете определить конструктор копирования и оператор назначения копирования для особого поведения, и ваш код будет работать. Но вы, по праву (!), Вынуждены это сделать.
Мораль этой истории: всегда используйте unique_ptr
в подобных ситуациях.