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.
Réponses
Trop de publicités?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.
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 deunique_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, carunique_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 deunique_ptr
. Donc, on doit construire un nouveauunique_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 unBase*
dans la classe de base. Dans la classe dérivée, cependant, elle est étendue via la covariance pour renvoyer une valeur deDerived*
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 ununique_ptr
et de le renvoyer par l'intermédiaire de la méthode actuelleclone()
qui est appelée depuis l'extérieur.
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;
};
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.
- Réponses précédentes
- Plus de réponses