Copier le constructeur pour une classe avec un ptr unique

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

74
demandé sur Nicol Bolas 2013-04-16 10:21:30

5 réponses

puisque le unique_ptr ne peut pas être partagé, vous devez soit copier son contenu en profondeur ou convertir le unique_ptr en 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 mentionné, utiliser un move-ctor au lieu d'un copy-ctor, mais cela donnerait une sémantique différente de votre classe. Un déménageur devrait rendre le membre mobile explicitement via std::move :

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

ayant un jeu complet des opérateurs nécessaires aussi conduit à

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 souhaitez utiliser votre classe dans un std::vector , en gros, vous avez à décider si le vecteur est l'unique propriétaire d'un objet, auquel cas il serait suffisante pour rendre la classe mobile, mais pas copiable. Si vous omettez le copy-ctor et le copy-assignment, le compilateur vous guidera sur la façon d'utiliser un vecteur std::avec des types ne comportant que des mouvements.

59
répondu Daniel Frey 2017-04-05 09:27:13

le cas habituel pour avoir un unique_ptr dans une classe est d'être en mesure d'utiliser l'héritage (sinon un objet simple ferait aussi bien, voir Rai). Pour ce cas, il n'y a pas de réponse appropriée dans ce thread jusqu'à présent .

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

pour ceci, un doit faire une copie profonde du pointeur contenu pour s'assurer que la classe dérivée est copiée correctement.

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

il y a essentiellement deux choses qui se passent ici:

  • le premier est l'ajout des constructeurs de copie et de déplacement, qui sont implicitement supprimés dans Foo en tant que constructeur de copie de unique_ptr est supprimé. Le constructeur move peut être ajouté simplement par = default ... ce qui est juste pour faire savoir au compilateur que le constructeur de déplacement habituel doit et non être supprimé (cela fonctionne, car unique_ptr a déjà un constructeur de déplacement qui peut être utilisé dans ce cas).

    pour le constructeur de copies de Foo , il n'y a pas de mécanisme similaire car il n'y a pas de constructeur de copies de unique_ptr . Donc, on a à construire un nouveau unique_ptr , remplissez-le avec une copie du pointee original, et utilisez-le comme membre de la classe copiée.

  • en cas d'héritage, la copie du pointé original doit être faite avec soin. La raison en est que faire une simple copie via std::unique_ptr<Base>(*ptr) dans le code ci-dessus aboutirait à couper, c.-à-d., seul le composant de base de l'objet est copié, alors que la partie dérivée est manquante.

    pour éviter cela, le la copie doit être faite via le modèle de clone. L'idée est de faire la copie via une fonction virtuelle clone_impl() qui renvoie une Base* dans la classe de base. Dans la classe dérivée, cependant, il est étendu via covariance pour retourner 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 classe de base Base* , l'envelopper dans un unique_ptr , et le retourner via la fonction actuelle clone() ce qui est appelé depuis l'extérieur.

15
répondu davidhigh 2017-04-06 22:39:58

essayez ce helper pour créer des copies profondes, et copiez 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;
    }

par exemple:

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

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};
10
répondu Scott Langham 2014-11-24 11:50:41

Daniel Frey mention à propos de la solution de copie,je voudrais parler de la façon de déplacer le 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 move constructor et move assignment

, vous pouvez 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:: déplacer parce qu'ils ont un nom std:: move indique au compilateur de transformer la valeur en valeur de référence quels que soient les paramètres Au sens technique, std:: move is analogie à quelque chose comme "std::rvalue"

après avoir déménagé, la ressource du unique_ptr est transférée à un autre unique_ptr

il y a de nombreux sujets qui documentent la référence de valeur; c'est un sujet assez facile à commencer par .

Edit:

L'objet déplacé restent valables mais non précisées de l'état .

c++ primer 5, ch13 aussi donnent une très bonne explication sur la façon de "déplacer" l'objet

5
répondu StereoMatching 2017-05-23 10:31:28

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
répondu Splash 2018-07-25 14:13:47