Copia costruttore per una class con unique_ptr

Come posso implementare un costruttore di copie per una class che ha una variabile membro unique_ptr ? Sto solo considerando C ++ 11.

Poiché unique_ptr non può essere condiviso, è necessario eseguire una copia completa del contenuto o convertire unique_ptr in 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; } 

Come menzionato da NPE, puoi usare un agente di movimento invece di un copy-ctor ma che risulterebbe in una semantica diversa della tua class. Un mittente dovrebbe rendere il membro mobile in modo esplicito tramite std::move :

 A( A&& a ) : up_( std::move( a.up_ ) ) {} 

Porta anche a una serie completa degli operatori necessari

 A& operator=( const A& a ) { up_.reset( new int( *a.up_ ) ); return *this, } A& operator=( A&& a ) { up_ = std::move( a.up_ ); return *this, } 

Se vuoi usare la tua class in un std::vector , in pratica devi decidere se il vettore deve essere il proprietario unico di un object, nel qual caso sarebbe sufficiente rendere la class mobile, ma non copiabile. Se si omettono il copy-ctor e il copy-assignment, il compilatore ti guiderà su come utilizzare un std :: vector con i tipi di spostamento.

Il solito caso per uno di avere un unique_ptr in una class è di essere in grado di usare l’ereditarietà (altrimenti un object semplice farebbe altrettanto, vedi RAII). Per questo caso, non esiste una risposta appropriata in questo thread fino ad ora .

Quindi, ecco il punto di partenza:

 struct Base { //some stuff }; struct Derived : public Base { //some stuff }; struct Foo { std::unique_ptr ptr; //points to Derived or some other derived class }; 

… e l’objective è, come detto, rendere Foo copiable.

Per fare ciò, è necessario eseguire una copia profonda del puntatore contenuto per garantire che la class derivata venga copiata correttamente.

Questo può essere ottenuto aggiungendo il seguente codice:

 struct Base { //some stuff auto clone() const { return std::unique_ptr(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 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; }; 

Ci sono fondamentalmente due cose che succedono qui:

  • Il primo è l’aggiunta di copia e sposta i costruttori, che vengono eliminati implicitamente in Foo quando il costruttore di unique_ptr di unique_ptr viene cancellato. Il costruttore di move può essere aggiunto semplicemente da = default … che è solo per far sapere al compilatore che il solito costruttore di move non deve essere cancellato (funziona, dato che unique_ptr ha già un costruttore di move che può essere usato in questo caso).

    Per il costruttore di copie di Foo , non esiste un meccanismo simile in quanto non esiste un costruttore di copia di unique_ptr . Quindi, si deve build un nuovo unique_ptr , riempirlo con una copia del pointee originale e usarlo come membro della class copiata.

  • Nel caso in cui sia coinvolta l’ereditarietà, la copia del pointee originale deve essere eseguita con attenzione. La ragione è che fare una semplice copia tramite std::unique_ptr(*ptr) nel codice sopra risulterebbe in slicing, cioè, solo il componente base dell’object viene copiato, mentre la parte derivata è mancante.

    Per evitare ciò, la copia deve essere eseguita tramite il pattern clone. L’idea è di fare la copia attraverso una funzione virtuale clone_impl() che restituisce una Base* nella class base. Nella class derivata, tuttavia, viene esteso tramite covariance per restituire un Derived* e questo puntatore punta a una copia appena creata della class derivata. La class base può quindi accedere a questo nuovo object tramite il puntatore della class base Base* , racchiuderlo in un unique_ptr e restituirlo tramite la funzione clone() effettiva chiamata dall’esterno.

Prova questo helper per creare copie profonde e far fronte quando l’origine unique_ptr è nullo.

  template< class T > std::unique_ptr copy_unique(const std::unique_ptr& source) { return source ? std::make_unique(*source) : nullptr; } 

Per esempio:

 class My { My( const My& rhs ) : member( copy_unique(rhs.member) ) { } // ... other methods private: std::unique_ptr member; }; 

Daniel Frey menziona sulla soluzione di copia, vorrei parlare di come spostare il unique_ptr

 #include  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 a_; }; 

Si chiamano move constructor e move assignment

potresti usarli in questo modo

 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 } 

È necessario eseguire il wrapping di a e c per std :: move perché hanno un nome std :: move sta dicendo al compilatore di trasformare il valore in riferimento di rvalue indipendentemente dai parametri In senso tecnico, std :: move è analogia con qualcosa di simile ” std :: rvalue”

Dopo lo spostamento, la risorsa di unique_ptr viene trasferita a un altro unique_ptr

Ci sono molti argomenti che documentano il riferimento di valore; questo è abbastanza facile per cominciare .

Modificare :

L’object spostato deve rimanere valido ma non specificato .

Anche il primer C ++ 5, ch13 fornisce un’ottima spiegazione su come “spostare” l’object

Suggerisco di usare make_unique

 class A { std::unique_ptr< int > up_; public: A( int i ) : up_(std::make_unique(i)) {} A( const A& a ) : up_(std::make_unique(*a.up_)) {}; int main() { A a( 42 ); A b = a; }