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.
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.
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 deunique_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, carunique_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 deunique_ptr
. Donc, on a à construire un nouveauunique_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 uneBase*
dans la classe de base. Dans la classe dérivée, cependant, il est étendu via covariance pour retourner unDerived*
, 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 baseBase*
, l'envelopper dans ununique_ptr
, et le retourner via la fonction actuelleclone()
ce qui est appelé depuis l'extérieur.
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;
};
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
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;
}