Конструктор копирования для класса с уникальным ПТР
Как реализовать конструктор копирования для класса, который имеет unique_ptr
переменной-члена? Я рассматриваю только C++11.
5 ответов:
С
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, но это приведет к другой семантике вашего класса. Move-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-assignment, компилятор будет указывать вам, как использовать std::vector с типами только для перемещения.
обычный случай для одного, чтобы иметь
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
, заполните его копией исходного указателя и используйте его в качестве члена скопированного класса.если речь идет о наследовании, то копия оригинального указателя должна быть сделана тщательно. Причина в том, что делать простую копию через
std::unique_ptr<Base>(*ptr)
в приведенном выше коде это приведет к нарезке, т. е. копируется только базовый компонент объекта, а производная часть отсутствует.чтобы избежать этого, копия должна быть сделана с помощью клон-шаблон. Идея в том, чтобы сделать копию через виртуальную функцию
clone_impl()
который возвращает aBase*
в базовом классе. Однако в производном классе он расширяется через ковариацию до возвратитьDerived*
, и этот указатель указывает на вновь созданный экземпляр производного класса. Затем базовый класс может получить доступ к этому новому объекту через указатель базового классаBase*
, оберните его вunique_ptr
, и вернуть его через фактическийclone()
функция, которая вызывается извне.
попробуйте этот помощник для создания глубоких копий и справиться, когда источник 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; };
Даниэль Фрей упомянул о решении для копирования, я бы рассказал о том, как переместить 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 переносится на другой unique_ptr
есть много тем, которые документируют ссылку rvalue;это довольно легко начать с.
Edit:
перемещаемого объекта остается действительным, но неопределенное состояние.
C++ primer 5, ch13 также дают очень хорошее объяснение о том, как" переместить " объект