88 votes

Quand doit-on utiliser des constructeurs de copie ?

Je sais que le compilateur C++ crée un constructeur de copie pour une classe. Dans quel cas devons-nous écrire un constructeur de copie défini par l'utilisateur ? Pouvez-vous donner quelques exemples ?

1 votes

L'un des cas pour écrire votre propre copie-acteur : Quand vous devez faire une copie profonde. Notez également que dès que vous créez un ctor, aucun ctor par défaut n'est créé pour vous (sauf si vous utilisez le mot-clé default).

76voto

sharptooth Points 93379

Le constructeur de copie généré par le compilateur effectue des copies de membres. Parfois, ce n'est pas suffisant. Par exemple :

class Class {
public:
    Class( const char* str );
    ~Class();
private:
    char* stored;
};

Class::Class( const char* str )
{
    stored = new char[srtlen( str ) + 1 ];
    strcpy( stored, str );
}

Class::~Class()
{
    delete[] stored;
}

dans ce cas, la copie par membre de stored ne dupliquera pas le tampon (seul le pointeur sera copié), donc la première copie détruite partageant le tampon appellera delete[] avec succès et la seconde se heurtera à un comportement non défini. Vous avez besoin d'une copie profonde du constructeur de copie (et de l'opérateur d'affectation également).

Class::Class( const Class& another )
{
    stored = new char[strlen(another.stored) + 1];
    strcpy( stored, another.stored );
}

void Class::operator = ( const Class& another )
{
    char* temp = new char[strlen(another.stored) + 1];
    strcpy( temp, another.stored);
    delete[] stored;
    stored = temp;
}

10 votes

Il n'effectue pas une copie bit à bit, mais une copie membre à membre qui, en particulier, fait appel aucteur de copie pour les membres de type classe.

7 votes

N'écrivez pas l'opérateur d'assignement comme ça. Ce n'est pas sûr pour les exceptions. (si le new lève une exception, l'objet est laissé dans un état indéfini avec store pointant sur une partie de la mémoire désallouée (désallouez la mémoire UNIQUEMENT après que toutes les opérations qui peuvent lever une exception se soient terminées avec succès)). Une solution simple est d'utiliser l'idium copy swap.

0 votes

@sharptooth 3ème ligne en partant du bas, vous avez delete stored[]; et je crois qu'il devrait être delete [] stored;

47voto

Matthieu M. Points 101624

Je suis un peu énervé que la règle de la Rule of Five n'a pas été cité.

Cette règle est très simple :

La règle des cinq :
Lorsque vous écrivez l'un des éléments suivants : Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor ou Move Assignment Operator, vous devez probablement écrire les quatre autres.

Mais il existe une directive plus générale que vous devez suivre, qui découle de la nécessité d'écrire du code sécurisé par les exceptions :

Chaque ressource doit être gérée par un objet dédié

Ici @sharptooth Le code de l'utilisateur est toujours (en grande partie) correct, mais s'il devait ajouter un deuxième attribut à sa classe, il ne le serait pas. Considérons la classe suivante :

class Erroneous
{
public:
  Erroneous();
  // ... others
private:
  Foo* mFoo;
  Bar* mBar;
};

Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}

Que se passe-t-il si new Bar Jets ? Comment supprimer l'objet pointé par mFoo ? Il existe des solutions (try/catch au niveau des fonctions ...), mais elles ne sont pas adaptées.

La bonne façon de gérer cette situation est d'utiliser des classes appropriées au lieu de pointeurs bruts.

class Righteous
{
public:
private:
  std::unique_ptr<Foo> mFoo;
  std::unique_ptr<Bar> mBar;
};

Avec la même mise en œuvre de constructeur (ou en fait, en utilisant make_unique ), j'ai maintenant une sécurité exceptionnelle gratuite ! !! N'est-ce pas excitant ? Et le meilleur de tout, je n'ai plus besoin de me soucier d'un destructeur approprié ! J'ai besoin d'écrire mon propre Copy Constructor y Assignment Operator cependant, parce que unique_ptr ne définit pas ces opérations... mais cela n'a pas d'importance ici ;)

Et donc, sharptooth La classe de l'enfant revisitée :

class Class
{
public:
  Class(char const* str): mData(str) {}
private:
  std::string mData;
};

Je ne sais pas pour vous, mais je trouve le mien plus facile ;)

0 votes

Pour C++ 11 - la règle des cinq qui ajoute à la règle des trois le constructeur de déplacement et l'opérateur d'affectation de déplacement.

1 votes

@Robb : Notez qu'en fait, comme le montre le dernier exemple, vous devriez généralement viser le paramètre Règle du zéro . Seules les classes techniques spécialisées (génériques) devraient se préoccuper de la gestion de l'information. un toutes les autres classes devraient utiliser ces pointeurs/conteneurs intelligents et ne pas s'en préoccuper.

0 votes

@MatthieuM. D'accord :-) J'ai mentionné la règle des cinq, puisque cette réponse est antérieure à C++11 et commence par les "Big Three", mais il faut préciser que maintenant les "Big Five" sont pertinents. Je ne veux pas décoter cette réponse car elle est correcte dans le contexte demandé.

6voto

Peter Ajtai Points 26377

Si vous avez une classe qui a un contenu alloué dynamiquement. Par exemple, vous stockez le titre d'un livre sous la forme d'un char * et définissez le titre avec new, copy ne fonctionnera pas.

Vous devriez écrire un constructeur de copie qui fait title = new char[length+1] et ensuite strcpy(title, titleIn) . Le constructeur de copie ne ferait qu'une copie "superficielle".

2voto

Prabhu Jayaraman Points 2145

Le constructeur Copy est appelé lorsqu'un objet est soit transmis par valeur, soit retourné par valeur, soit explicitement copié. S'il n'y a pas de constructeur de copie, c++ crée un constructeur de copie par défaut qui fait une copie superficielle. Si l'objet n'a pas de pointeurs vers de la mémoire allouée dynamiquement, la copie superficielle fera l'affaire.

0voto

seand Points 3426

C'est souvent une bonne idée de désactiver le copy ctor et l'operator=, sauf si la classe en a spécifiquement besoin. Cela peut éviter des inefficacités telles que le passage d'un argument par valeur alors que la référence est prévue. De plus, les méthodes générées par le compilateur peuvent être invalides.

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