133 votes

Copie du constructeur d'une classe avec unique_ptr

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

105voto

Daniel Frey Points 30752

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

Vous pouvez, comme NPE l'a mentionné, utiliser un vecteur de déplacement au lieu d'un vecteur de copie, mais cela entraînerait une sémantique différente de votre classe. Un move-ctor devrait rendre le membre déplaçable explicitement par l'intermédiaire de std::move :

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

Disposer d'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 décider si le vecteur doit être le propriétaire unique d'un objet, auquel cas il suffirait de rendre la classe déplaçable, mais pas copiable. Si vous laissez de côté le copy-ctor et le copy-assignment, le compilateur vous guidera sur la manière d'utiliser un std::vector avec des types à déplacer uniquement.

70voto

davidhigh Points 827

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

Voici donc 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 le but est, comme on l'a dit, de rendre Foo copiable.

Pour cela, il faut faire un copie profonde du pointeur contenu pour s'assurer que la classe dérivée est copiée correctement.

Ceci peut être réalisé 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 :

  • La première est l'ajout des constructeurs copy et move, qui sont implicitement supprimés dans l'application Foo comme le constructeur de copie de unique_ptr est supprimé. Le constructeur de déplacement peut être ajouté simplement par = default ... qui est juste pour faire savoir au compilateur que le constructeur habituel de move doit no être supprimé (cela fonctionne, car unique_ptr possède déjà un constructeur move 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, on doit construire un nouveau unique_ptr , le remplir avec une copie du pointeur original, et l'utiliser comme membre de la classe copiée.

  • En cas d'héritage, la copie du pointeur original doit être effectuée avec soin. La raison en est qu'une simple copie via std::unique_ptr<Base>(*ptr) dans le code ci-dessus entraînerait un découpage en tranches, c'est-à-dire que seul le composant de base de l'objet est copié, alors que la partie dérivée est absente.

    Pour éviter cela, la copie doit être effectuée via le modèle 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, elle est étendue via la covariance pour renvoyer une valeur de 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* et l'envelopper dans un unique_ptr et de le renvoyer par l'intermédiaire de la méthode actuelle clone() qui est appelée depuis l'extérieur.

16voto

Scott Langham Points 17447

Essayez cette aide pour créer des copies profondes, et faire face lorsque la source unique_ptr est nulle.

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

Eg :

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

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};

6voto

StereoMatching Points 1166

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 ceci

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 envelopper a et c par std::move car ils ont un nom. std::move indique au compilateur de transformer la valeur en rvalue référence quel que soit le paramètre Dans un sens technique, std::move est une analogie avec quelque chose comme "std::rvalue".

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

Il existe de nombreux sujets qui documentent la référence rvalue ; c'est une question assez facile pour commencer .

Edit :

L'objet déplacé reste valide mais état non spécifié .

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

4voto

Splash Points 392

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X