- Que signifie copier un objet ?
- Que sont le constructeur de copie et l' opérateur d'affectation de copie ?
- Quand dois-je les déclarer moi-même?
- Comment empêcher la copie de mes objets?
c++
copy-constructor
assignment-operator
c++-faq
rule-of-three
fredoverflow
la source
la source
c++-faq
wiki du tag avant de voter pour fermer .Réponses:
introduction
C ++ traite les variables de types définis par l'utilisateur avec une sémantique de valeur . Cela signifie que les objets sont implicitement copiés dans divers contextes, et nous devons comprendre ce que signifie réellement "copier un objet".
Prenons un exemple simple:
(Si la partie vous laisse perplexe
name(name), age(age)
, cela s'appelle une liste d'initialisation de membre .)Fonctions spéciales des membres
Que signifie copier un
person
objet? Lamain
fonction affiche deux scénarios de copie distincts. L'initialisationperson b(a);
est effectuée par le constructeur de copie . Son travail consiste à construire un nouvel objet en fonction de l'état d'un objet existant. L'affectationb = a
est effectuée par l' opérateur d'affectation de copie . Son travail est généralement un peu plus compliqué, car l'objet cible est déjà dans un état valide qui doit être traité.Puisque nous n'avons déclaré ni le constructeur de copie ni l'opérateur d'affectation (ni le destructeur) nous-mêmes, ceux-ci sont implicitement définis pour nous. Citation de la norme:
Par défaut, copier un objet signifie copier ses membres:
Définitions implicites
Les fonctions membres spéciales définies implicitement pour
person
ressemblent à ceci:La copie par membre est exactement ce que nous voulons dans ce cas:
name
etage
est copiée, nous obtenons donc unperson
objet indépendant et indépendant . Le destructeur défini implicitement est toujours vide. C'est également très bien dans ce cas puisque nous n'avons acquis aucune ressource dans le constructeur. Les destructeurs des membres sont implicitement appelés une fois leperson
destructeur terminé:Gérer les ressources
Alors, quand devrions-nous déclarer explicitement ces fonctions membres spéciales? Lorsque notre classe gère une ressource , c'est-à-dire lorsqu'un objet de la classe est responsable de cette ressource. Cela signifie généralement que la ressource est acquise dans le constructeur (ou transmise au constructeur) et libérée dans le destructeur.
Remontons le temps au C ++ pré-standard. Il n'y avait rien de tel
std::string
, et les programmeurs étaient amoureux des pointeurs. Laperson
classe aurait pu ressembler à ceci:Aujourd'hui encore, les gens écrivent des cours dans ce style et ont des ennuis: " J'ai poussé une personne dans un vecteur et maintenant j'ai des erreurs de mémoire folles! " Rappelez-vous que par défaut, copier un objet signifie copier ses membres, mais copier le
name
membre simplement copie un pointeur, pas le tableau de caractères vers lequel il pointe! Cela a plusieurs effets désagréables:a
peuvent être observées viab
.b
détruit, sea.name
trouve un pointeur suspendu.a
est détruit, la suppression du pointeur pendant entraîne un comportement indéfini .name
indiqué avant l'affectation, tôt ou tard, vous obtiendrez des fuites de mémoire partout.Définitions explicites
Étant donné que la copie par membre n'a pas l'effet souhaité, nous devons définir explicitement le constructeur de copie et l'opérateur d'affectation de copie pour effectuer des copies complètes du tableau de caractères:
Notez la différence entre l'initialisation et l'affectation: nous devons supprimer l'ancien état avant de l'affecter
name
pour éviter les fuites de mémoire. Nous devons également nous protéger contre l'auto-affectation du formulairex = x
. Sans cette vérification,delete[] name
supprimerait le tableau contenant la source de chaîne, parce que quand vous écrivezx = x
, à la foisthis->name
etthat.name
contiennent le même pointeur.Sécurité d'exception
Malheureusement, cette solution échouera si
new char[...]
lève une exception en raison de l'épuisement de la mémoire. Une solution possible consiste à introduire une variable locale et à réorganiser les instructions:Cela prend également en charge l'auto-affectation sans vérification explicite. Une solution encore plus robuste à ce problème est l' idiome de copie et d'échange , mais je n'entrerai pas dans les détails de la sécurité des exceptions ici. J'ai seulement mentionné des exceptions pour faire le point suivant: Écrire des classes qui gèrent des ressources est difficile.
Ressources non copiables
Certaines ressources ne peuvent pas ou ne doivent pas être copiées, comme les descripteurs de fichiers ou les mutex. Dans ce cas, déclarez simplement le constructeur de copie et l'opérateur d'affectation de copie comme
private
sans donner de définition:Alternativement, vous pouvez les hériter
boost::noncopyable
ou les déclarer supprimés (en C ++ 11 et supérieur):La règle des trois
Parfois, vous devez implémenter une classe qui gère une ressource. (Ne gérez jamais plusieurs ressources dans une seule classe, cela ne fera que causer de la douleur.) Dans ce cas, rappelez-vous la règle des trois :
(Malheureusement, cette "règle" n'est pas appliquée par la norme C ++ ou par tout compilateur que je connaisse.)
La règle des cinq
À partir de C ++ 11, un objet possède 2 fonctions membres spéciales supplémentaires: le constructeur de déplacement et l'affectation de déplacement. La règle des cinq États met également en œuvre ces fonctions.
Un exemple avec les signatures:
La règle du zéro
La règle de 3/5 est également appelée la règle de 0/3/5. La partie zéro de la règle indique que vous êtes autorisé à n'écrire aucune des fonctions membres spéciales lors de la création de votre classe.
Conseil
La plupart du temps, vous n'avez pas besoin de gérer vous-même une ressource, car une classe existante telle que
std::string
déjà le fait pour vous. Comparez simplement le code simple utilisant unstd::string
membre à l'alternative alambiquée et sujette aux erreurs en utilisant unchar*
et vous devriez être convaincu. Tant que vous restez à l'écart des membres du pointeur brut, la règle de trois ne concernera probablement pas votre propre code.la source
La règle des trois est une règle de base pour C ++, en disant essentiellement
La raison en est que les trois sont généralement utilisés pour gérer une ressource, et si votre classe gère une ressource, elle doit généralement gérer la copie ainsi que la libération.
S'il n'y a pas de bonne sémantique pour copier la ressource gérée par votre classe, alors envisagez d'interdire la copie en déclarant (pas en définissant ) le constructeur de copie et l'opérateur d'affectation comme
private
.(Notez que la nouvelle version à venir de la norme C ++ (qui est C ++ 11) ajoute la sémantique de déplacement à C ++, ce qui changera probablement la règle des trois. Cependant, je sais trop peu de choses à ce sujet pour écrire une section C ++ 11 à propos de la règle de trois.)
la source
boost::noncopyable
). Cela peut aussi être beaucoup plus clair. Je pense que C ++ 0x et la possibilité de "supprimer" des fonctions pourraient aider ici, mais j'ai oublié la syntaxe: /noncopyable
partie de la librairie standard, je ne considère pas cela comme une grande amélioration. (Oh, et si vous avez oublié la syntaxe de suppression, vous avez oublié plus que je n'ai jamais connu.:)
)La loi des trois grands est celle indiquée ci-dessus.
Un exemple simple, en anglais simple, du type de problème qu'il résout:
Destructeur non par défaut
Vous avez alloué de la mémoire dans votre constructeur et vous devez donc écrire un destructeur pour le supprimer. Sinon, vous provoquerez une fuite de mémoire.
Vous pourriez penser que c'est du travail accompli.
Le problème sera, si une copie est faite de votre objet, alors la copie pointera vers la même mémoire que l'objet d'origine.
Une fois que l'un d'eux supprime la mémoire de son destructeur, l'autre aura un pointeur vers une mémoire invalide (c'est ce qu'on appelle un pointeur suspendu) lorsqu'il essaie de l'utiliser, les choses vont devenir poilues.
Par conséquent, vous écrivez un constructeur de copie afin qu'il alloue aux nouveaux objets leurs propres morceaux de mémoire à détruire.
Opérateur d'affectation et constructeur de copie
Vous avez alloué de la mémoire dans votre constructeur à un pointeur membre de votre classe. Lorsque vous copiez un objet de cette classe, l'opérateur d'affectation par défaut et le constructeur de copie copient la valeur de ce pointeur de membre vers le nouvel objet.
Cela signifie que le nouvel objet et l'ancien objet pointeront vers le même morceau de mémoire, donc lorsque vous le modifiez dans un objet, il sera également modifié pour l'autre objet. Si un objet supprime cette mémoire, l'autre continuera d'essayer de l'utiliser - eek.
Pour résoudre ce problème, vous écrivez votre propre version du constructeur de copie et de l'opérateur d'affectation. Vos versions allouent une mémoire distincte aux nouveaux objets et copient les valeurs vers lesquelles pointe le premier pointeur plutôt que son adresse.
la source
Fondamentalement, si vous avez un destructeur (pas le destructeur par défaut), cela signifie que la classe que vous avez définie dispose d'une allocation de mémoire. Supposons que la classe soit utilisée à l'extérieur par un code client ou par vous.
Si MyClass n'a que quelques membres de type primitif, un opérateur d'affectation par défaut fonctionnerait, mais s'il a des membres de pointeur et des objets qui n'ont pas d'opérateurs d'affectation, le résultat serait imprévisible. Par conséquent, nous pouvons dire que s'il y a quelque chose à supprimer dans le destructeur d'une classe, nous pourrions avoir besoin d'un opérateur de copie en profondeur, ce qui signifie que nous devrions fournir un constructeur de copie et un opérateur d'affectation.
la source
Que signifie copier un objet? Il existe plusieurs façons de copier des objets - parlons des 2 types auxquels vous faites probablement référence - la copie profonde et la copie superficielle.
Puisque nous sommes dans un langage orienté objet (ou du moins le supposons), disons que vous avez un morceau de mémoire alloué. Puisqu'il s'agit d'un langage OO, nous pouvons facilement faire référence aux morceaux de mémoire que nous allouons car ce sont généralement des variables primitives (ints, chars, octets) ou des classes que nous avons définies qui sont faites de nos propres types et primitives. Disons donc que nous avons une classe de voitures comme suit:
Une copie profonde est si nous déclarons un objet puis créons une copie complètement séparée de l'objet ... nous nous retrouvons avec 2 objets dans 2 ensembles de mémoire complètement.
Faisons maintenant quelque chose d'étrange. Disons que car2 est mal programmé ou intentionnellement destiné à partager la mémoire réelle de car1. (C'est généralement une erreur de le faire et dans les classes, c'est généralement la couverture dont il est question.) Imaginez que chaque fois que vous posez des questions sur car2, vous résolvez vraiment un pointeur vers l'espace mémoire de car1 ... c'est plus ou moins ce qu'une copie superficielle est.
Donc, quelle que soit la langue dans laquelle vous écrivez, faites très attention à ce que vous voulez dire quand il s'agit de copier des objets, car la plupart du temps vous voulez une copie complète.
Que sont le constructeur de copie et l'opérateur d'affectation de copie? Je les ai déjà utilisés ci-dessus. Le constructeur de copie est appelé lorsque vous tapez du code tel que
Car car2 = car1;
Essentiellement si vous déclarez une variable et l'affectez sur une seule ligne, c'est alors que le constructeur de copie est appelé. L'opérateur d'affectation est ce qui se passe lorsque vous utilisez un signe égal -car2 = car1;
. L'aviscar2
n'est pas déclaré dans la même déclaration. Les deux morceaux de code que vous écrivez pour ces opérations sont probablement très similaires. En fait, le modèle de conception typique a une autre fonction que vous appelez pour tout définir une fois que vous êtes convaincu que la copie / affectation initiale est légitime - si vous regardez le code long que j'ai écrit, les fonctions sont presque identiques.Quand dois-je les déclarer moi-même? Si vous n'écrivez pas de code à partager ou à produire d'une manière ou d'une autre, vous n'avez vraiment besoin de les déclarer que lorsque vous en avez besoin. Vous devez être conscient de ce que fait votre langage de programme si vous choisissez de l'utiliser «par accident» et n'en avez pas créé - c'est-à-dire que vous obtenez le compilateur par défaut. J'utilise rarement des constructeurs de copie par exemple, mais les remplacements d'opérateurs d'affectation sont très courants. Saviez-vous que vous pouvez également remplacer ce que signifie addition, soustraction, etc.?
Comment empêcher la copie de mes objets? Remplacer toutes les façons d'allouer de la mémoire à votre objet avec une fonction privée est un début raisonnable. Si vous ne voulez vraiment pas que les gens les copient, vous pouvez le rendre public et alerter le programmeur en lançant une exception et en ne copiant pas l'objet.
la source
La règle de trois stipule que si vous déclarez
alors vous devez déclarer les trois. Il est né de l'observation que la nécessité de reprendre le sens d'une opération de copie provenait presque toujours de la classe effectuant une sorte de gestion des ressources, et cela impliquait presque toujours que
quelle que soit la gestion des ressources effectuée dans une opération de copie, elle devait probablement l'être dans l'autre opération de copie et
le destructeur de classe participerait également à la gestion de la ressource (la libérant généralement). La ressource classique à gérer était la mémoire, et c'est pourquoi toutes les classes Standard Library qui gèrent la mémoire (par exemple, les conteneurs STL qui effectuent la gestion dynamique de la mémoire) déclarent toutes «les trois grands»: à la fois des opérations de copie et un destructeur.
Une conséquence de la règle de trois est que la présence d'un destructeur déclaré par l'utilisateur indique qu'une copie simple par membre est peu susceptible d'être appropriée pour les opérations de copie dans la classe. Cela, à son tour, suggère que si une classe déclare un destructeur, les opérations de copie ne devraient probablement pas être générées automatiquement, car elles ne feraient pas la bonne chose. Au moment où C ++ 98 a été adopté, l'importance de ce raisonnement n'était pas pleinement appréciée, donc en C ++ 98, l'existence d'un destructeur déclaré par l'utilisateur n'a eu aucun impact sur la volonté des compilateurs de générer des opérations de copie. Cela continue d'être le cas en C ++ 11, mais uniquement parce que restreindre les conditions dans lesquelles les opérations de copie sont générées casserait trop de code hérité.
Déclarez le constructeur de copie et l'opérateur d'affectation de copie en tant que spécificateur d'accès privé.
Dans C ++ 11 et suivants, vous pouvez également déclarer le constructeur de copie et l'opérateur d'affectation supprimés
la source
De nombreuses réponses existantes concernent déjà le constructeur de copie, l'opérateur d'affectation et le destructeur. Cependant, dans le post C ++ 11, l'introduction de la sémantique de mouvement peut étendre cela au-delà de 3.
Récemment, Michael Claisse a donné une conférence qui touche à ce sujet: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class
la source
La règle de trois en C ++ est un principe fondamental de la conception et du développement de trois exigences selon lesquelles s'il existe une définition claire dans l'une des fonctions membres suivantes, le programmeur doit définir les deux autres fonctions membres ensemble. A savoir les trois fonctions membres suivantes sont indispensables: destructeur, constructeur de copie, opérateur d'affectation de copie.
Le constructeur de copie en C ++ est un constructeur spécial. Il est utilisé pour créer un nouvel objet, qui est le nouvel objet équivalent à une copie d'un objet existant.
L'opérateur d'affectation de copie est un opérateur d'affectation spécial qui est généralement utilisé pour spécifier un objet existant à d'autres du même type d'objet.
Il y a des exemples rapides:
la source