web-dev-qa-db-fra.com

Copier le constructeur d'une classe avec unique_ptr

Comment implémenter un constructeur de copie pour une classe qui a un unique_ptr variable membre? Je ne considère que C++ 11.

88
codefx

Depuis le unique_ptr ne peut pas être partagé, vous devez copier son contenu en profondeur ou convertir le fichier unique_ptr à un 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;
}

Comme NPE l'a mentionné, vous pouvez utiliser un move-ctor au lieu d'un copy-ctor, mais cela entraînerait une sémantique différente de votre classe. Un déménageur doit rendre le membre explicitement amovible via std::move:

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

Avoir un ensemble complet d'opérateurs nécessaires conduit également à

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

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

Si vous voulez utiliser votre classe dans un std::vector, vous devez en principe décider si le vecteur doit être l'unique propriétaire d'un objet. Dans ce cas, il suffirait de rendre la classe mobile, mais non copiable. Si vous laissez de côté le copy-ctor et le copy-assign, le compilateur vous guidera sur la façon d'utiliser un std :: vector avec des types à déplacement seulement.

68
Daniel Frey

Le cas habituel pour avoir un unique_ptr Dans une classe est de pouvoir utiliser l'héritage (sinon un objet simple le ferait souvent aussi, voir RAII). Pour ce cas, il n'y a pas de réponse appropriée dans ce fil jusqu'à présent .

Alors, voici le point de départ:

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
};

... et l'objectif est, comme dit, de rendre Foo copiable.

Pour cela, vous devez effectuer une copie complète du pointeur contenu pour vous assurer que la classe dérivée est copiée correctement.

Cela peut être accompli en ajoutant le code suivant:

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;
};

Il y a essentiellement deux choses qui se passent ici:

  • Le premier est l'ajout de constructeurs copier et déplacer, qui sont implicitement supprimés dans Foo, car le constructeur de copie de unique_ptr Est supprimé. Le constructeur de mouvements peut être ajouté simplement par = default ... ce qui consiste simplement à informer le compilateur que le constructeur de mouvements habituel doit pas être supprimé (cela fonctionne, car unique_ptr a déjà un constructeur de déménagement qui peut être utilisé dans ce cas).

    Pour le constructeur de copie de Foo, il n'y a pas de mécanisme similaire car il n'y a pas de constructeur de copie de unique_ptr. Donc, il faut construire un nouveau unique_ptr, Le remplir avec une copie de la pointee originale et l'utiliser en tant que membre de la classe copiée.

  • En cas d’héritage, la copie de la pointe originale doit être faite avec soin. La raison en est que faire une copie simple via std::unique_ptr<Base>(*ptr) dans le code ci-dessus entraînerait un découpage, c’est-à-dire que seul le composant de base de l’objet est copié, tandis que la pièce dérivée est manquante.

    Pour éviter cela, la copie doit être effectuée via le motif de clonage. L'idée est de faire la copie via une fonction virtuelle clone_impl() qui renvoie un Base* Dans la classe de base. Dans la classe dérivée, cependant, il est étendu via la covariance pour renvoyer un Derived*, Et ce pointeur pointe vers une copie nouvellement créée de la classe dérivée. La classe de base peut alors accéder à ce nouvel objet via le pointeur de la classe de base Base*, L'enrouler dans un unique_ptr Et le renvoyer via la fonction clone() qui est appelée à partir de l'extérieur.

26
davidhigh

Essayez cette aide pour créer des copies complètes, et gérer lorsque la source unique_ptr est null.

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

Par exemple:

class My
{
    My( const My& rhs )
        : member( copy_unique(rhs.member) )
    {
    }

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};
10
Scott Langham

Daniel Frey a parlé de la solution de copie, je voudrais parler de la façon de déplacer l'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_;
};

Ils sont appelés constructeur de déplacement et affectation de déplacement

vous pourriez les utiliser comme ç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

}

Vous devez encapsuler a et c avec std :: move car ils ont un nom. Std :: move demande au compilateur de transformer la valeur en référence rvalue quels que soient les paramètres. Au sens technique, std :: move est une analogie similaire à " std :: rvalue "

Après le déplacement, la ressource de unique_ptr est transférée vers un autre unique_ptr.

Il existe de nombreux sujets documentant la référence à la valeur; c'est assez facile pour commencer .

Modifier :

L'objet déplacé doit rester un état valide mais non spécifié .

L'apprêt C++ 5, ch13 donne également une très bonne explication sur la façon de "déplacer" l'objet

5
StereoMatching

Je suggère d'utiliser 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;
}
1
Splash