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? Peux-tu donner quelques exemples?
c++
copy-constructor
penguru
la source
la source
Réponses:
Le constructeur de copie généré par le compilateur effectue une copie par membre. Parfois, cela ne suffit pas. 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 du
stored
membre par membre ne dupliquera pas le tampon (seul le pointeur sera copié), donc le premier à être détruit en partageant la copie du tampon appelleradelete[]
avec succès et le second fonctionnera dans un comportement indéfini. Vous avez besoin d'un constructeur de copie de copie profonde (et d'un 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; }
la source
delete stored[];
et je pense que cela devrait êtredelete [] stored;
std::string
. L'idée générale est que seules les classes utilitaires qui gèrent les ressources doivent surcharger les trois grands, et que toutes les autres classes devraient simplement utiliser ces classes utilitaires, éliminant ainsi le besoin de définir l'un des trois grands.Je suis un peu énervé que la règle du
Rule of Five
ne soit pas citée.Cette règle est très simple:
Mais il existe une directive plus générale que vous devriez suivre, qui découle de la nécessité d'écrire du code sans exception:
Voici
@sharptooth
le code « est encore ( la plupart du temps) bien, s'il était d'ajouter un second attribut à sa classe , il ne serait pas. Considérez 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
jette? Comment supprimer l'objet pointé parmFoo
? Il existe des solutions (niveau de fonction try / catch ...), elles ne sont tout simplement pas mises à l'échelle.La bonne façon de gérer la 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 implémentation de constructeur (ou en fait, en utilisant
make_unique
), j'ai maintenant la sécurité d'exception gratuitement !!! N'est-ce pas excitant? Et le meilleur de tous, je n'ai plus besoin de me soucier d'un destructeur approprié! J'ai besoin d'écrire le mienCopy Constructor
etAssignment Operator
cependant, carunique_ptr
ne définit pas ces opérations ... mais cela n'a pas d'importance ici;)Et donc,
sharptooth
la classe de s revisitée:class Class { public: Class(char const* str): mData(str) {} private: std::string mData; };
Je ne sais pas pour toi, mais je trouve le mien plus facile;)
la source
Je peux me rappeler de ma pratique et penser aux cas suivants où l'on doit traiter explicitement de déclarer / définir le constructeur de copie. J'ai regroupé les cas en deux catégories
Exactitude / Sémantique
Je place dans cette section les cas où la déclaration / définition du constructeur de copie est nécessaire pour le bon fonctionnement des programmes utilisant ce type.
Après avoir lu cette section, vous découvrirez plusieurs écueils permettant au compilateur de générer lui-même le constructeur de copie. Par conséquent, comme seand l'a noté dans sa réponse , il est toujours prudent de désactiver la possibilité de copie pour une nouvelle classe et de l' activer délibérément plus tard lorsque cela est vraiment nécessaire.
Comment rendre une classe non copiable en C ++ 03
Déclarez un constructeur de copie privé et ne fournissez pas d'implémentation pour celui-ci (de sorte que la construction échoue à l'étape de liaison même si les objets de ce type sont copiés dans la propre portée de la classe ou par ses amis).
Comment rendre une classe non copiable en C ++ 11 ou plus récent
Déclarez le constructeur de copie avec
=delete
à la fin.Copie superficielle ou profonde
C'est le cas le mieux compris et en fait le seul mentionné dans les autres réponses. shaprtooth l' a assez bien couvert . Je veux seulement ajouter que les ressources profondément copiées qui devraient être la propriété exclusive de l'objet peuvent s'appliquer à tout type de ressources, dont la mémoire allouée dynamiquement n'est qu'un type. Si nécessaire, la copie en profondeur d'un objet peut également nécessiter
Objets auto-enregistrés
Considérons une classe où tous les objets - peu importe comment ils ont été construits - DOIVENT être enregistrés d'une manière ou d'une autre. Quelques exemples:
L'exemple le plus simple: maintenir le nombre total d'objets actuellement existants. L'enregistrement d'objets consiste simplement à incrémenter le compteur statique.
Un exemple plus complexe est d'avoir un registre singleton, où les références à tous les objets existants de ce type sont stockées (afin que les notifications puissent être envoyées à tous).
Les pointeurs intelligents comptés par référence peuvent être considérés comme un cas particulier dans cette catégorie: le nouveau pointeur "s'enregistre" avec la ressource partagée plutôt que dans un registre global.
Une telle opération d'auto-enregistrement doit être effectuée par N'IMPORTE QUEL constructeur du type et le constructeur de copie ne fait pas exception.
Objets avec références croisées internes
Certains objets peuvent avoir une structure interne non triviale avec des références croisées directes entre leurs différents sous-objets (en fait, une seule référence croisée interne suffit pour déclencher ce cas). Le constructeur de copie fourni par le compilateur cassera les associations intra-objets internes , les convertissant en associations inter-objets .
Un exemple:
struct MarriedMan; struct MarriedWoman; struct MarriedMan { // ... MarriedWoman* wife; // association }; struct MarriedWoman { // ... MarriedMan* husband; // association }; struct MarriedCouple { MarriedWoman wife; // aggregation MarriedMan husband; // aggregation MarriedCouple() { wife.husband = &husband; husband.wife = &wife; } }; MarriedCouple couple1; // couple1.wife and couple1.husband are spouses MarriedCouple couple2(couple1); // Are couple2.wife and couple2.husband indeed spouses? // Why does couple2.wife say that she is married to couple1.husband? // Why does couple2.husband say that he is married to couple1.wife?
Seuls les objets répondant à certains critères peuvent être copiés
Il peut y avoir des classes où les objets peuvent être copiés en toute sécurité lorsqu'ils sont dans un état (par exemple, l'état construit par défaut) et ne peuvent pas être copiés autrement. Si nous voulons autoriser la copie d'objets sûrs pour la copie, alors - si la programmation est défensive - nous avons besoin d'une vérification à l'exécution dans le constructeur de copie défini par l'utilisateur.
Sous-objets non copiables
Parfois, une classe qui devrait être copiable regroupe des sous-objets non copiables. Habituellement, cela se produit pour les objets avec un état non observable (ce cas est décrit plus en détail dans la section "Optimisation" ci-dessous). Le compilateur aide simplement à reconnaître ce cas.
Sous-objets quasi-copiables
Une classe, qui devrait être copiable, peut agréger un sous-objet d'un type quasi-copiable. Un type quasi-copiable ne fournit pas de constructeur de copie au sens strict, mais possède un autre constructeur qui permet de créer une copie conceptuelle de l'objet. La raison pour laquelle un type est quasi-copiable est lorsqu'il n'y a pas d'accord complet sur la sémantique de copie du type.
Ensuite, la décision finale est laissée aux utilisateurs de ce type - lors de la copie d'objets, ils doivent spécifier explicitement (via des arguments supplémentaires) la méthode de copie prévue.
Dans le cas d'une approche non défensive de la programmation, il est également possible qu'un constructeur de copie régulier et un constructeur de quasi-copie soient présents. Cela peut se justifier lorsque, dans la grande majorité des cas, une seule méthode de copie doit être appliquée, alors que dans des situations rares mais bien comprises, d'autres méthodes de copie doivent être utilisées. Ensuite, le compilateur ne se plaindra pas qu'il est incapable de définir implicitement le constructeur de copie; il sera de la seule responsabilité des utilisateurs de se souvenir et de vérifier si un sous-objet de ce type doit être copié via un constructeur de quasi-copie.
Ne pas copier l'état qui est fortement associé à l'identité de l'objet
Dans de rares cas, un sous-ensemble de l' état observable de l'objet peut constituer (ou être considéré) une partie inséparable de l'identité de l'objet et ne devrait pas être transférable à d'autres objets (bien que cela puisse être quelque peu controversé).
Exemples:
L'UID de l'objet (mais celui-ci appartient également au cas "auto-enregistrement" d'en haut, puisque l'identifiant doit être obtenu dans un acte d'auto-enregistrement).
Historique de l'objet (par exemple la pile Undo / Redo) dans le cas où le nouvel objet ne doit pas hériter de l'historique de l'objet source, mais plutôt commencer par un seul élément d'historique " Copié à <HEURE> depuis <OTHER_OBJECT_ID> ".
Dans de tels cas, le constructeur de copie doit ignorer la copie des sous-objets correspondants.
Application de la signature correcte du constructeur de copie
La signature du constructeur de copie fourni par le compilateur dépend des constructeurs de copie disponibles pour les sous-objets. Si au moins un sous-objet n'a pas de constructeur de copie réel (prenant l'objet source par référence constante) mais a à la place un constructeur de copie mutant (prenant l'objet source par référence non constante), le compilateur n'aura pas le choix mais pour déclarer implicitement puis définir un constructeur de copie mutant.
Maintenant, que se passe-t-il si le constructeur de copie "mutant" du type du sous-objet ne mute pas réellement l'objet source (et a été simplement écrit par un programmeur qui ne connaît pas le
const
mot clé)? Si nous ne pouvons pas réparer ce code en ajoutant le code manquantconst
, alors l'autre option est de déclarer notre propre constructeur de copie défini par l'utilisateur avec une signature correcte et de commettre le péché de passer à unconst_cast
.Copie sur écriture (COW)
Un conteneur COW qui a donné des références directes à ses données internes DOIT être copié en profondeur au moment de la construction, sinon il peut se comporter comme un descripteur de comptage de références.
Optimisation
Dans les cas suivants, vous voudrez peut-être / devrez définir votre propre constructeur de copie par souci d'optimisation:
Optimisation de la structure lors de la copie
Considérez un conteneur qui prend en charge les opérations de suppression d'élément, mais qui peut le faire en marquant simplement l'élément supprimé comme supprimé, et recycler son emplacement ultérieurement. Lorsqu'une copie d'un tel conteneur est réalisée, il peut être judicieux de compacter les données survivantes plutôt que de conserver les emplacements "supprimés" tels quels.
Ignorer la copie de l'état non observable
Un objet peut contenir des données qui ne font pas partie de son état observable. Habituellement, il s'agit de données mises en cache / mémorisées accumulées pendant la durée de vie de l'objet afin d'accélérer certaines opérations de requête lentes effectuées par l'objet. Il est prudent de ne pas copier ces données car elles seront recalculées lorsque (et si!) Les opérations pertinentes sont effectuées. La copie de ces données peut être injustifiée, car elle peut être rapidement invalidée si l'état observable de l'objet (à partir duquel les données mises en cache sont dérivées) est modifié par des opérations de mutation (et si nous n'allons pas modifier l'objet, pourquoi créons-nous un copier alors?)
Cette optimisation n'est justifiée que si les données auxiliaires sont importantes par rapport aux données représentant l'état observable.
Désactiver la copie implicite
C ++ permet de désactiver la copie implicite en déclarant le constructeur de copie
explicit
. Ensuite, les objets de cette classe ne peuvent pas être passés dans des fonctions et / ou renvoyés par des fonctions par valeur. Cette astuce peut être utilisée pour un type qui semble léger mais qui est en effet très coûteux à copier (cependant, le rendre quasi-copiable pourrait être un meilleur choix).TODOs
la source
Si vous avez une classe qui a un contenu alloué dynamiquement. Par exemple, vous stockez le titre d'un livre sous forme de caractère * et définissez le titre avec new, la copie ne fonctionnera pas.
Vous auriez à écrire un constructeur de copie qui fait
title = new char[length+1]
et ensuitestrcpy(title, titleIn)
. Le constructeur de copie ferait simplement une copie "superficielle".la source
Copy Constructor est appelé lorsqu'un objet est passé par valeur, renvoyé par valeur ou copié explicitement. 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 la mémoire allouée dynamiquement, une copie superficielle fera l'affaire.
la source
C'est souvent une bonne idée de désactiver copy ctor et 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 lorsque la référence est prévue. Les méthodes générées par le compilateur peuvent également être invalides.
la source
Considérons ci-dessous l'extrait de code:
class base{ int a, *p; public: base(){ p = new int; } void SetData(int, int); void ShowData(); base(const base& old_ref){ //No coding present. } }; void base :: ShowData(){ cout<<this->a<<" "<<*(this->p)<<endl; } void base :: SetData(int a, int b){ this->a = a; *(this->p) = b; } int main(void) { base b1; b1.SetData(2, 3); b1.ShowData(); base b2 = b1; //!! Copy constructor called. b2.ShowData(); return 0; }
Output: 2 3 //b1.ShowData(); 1996774332 1205913761 //b2.ShowData();
b2.ShowData();
donne une sortie indésirable car il existe un constructeur de copie défini par l'utilisateur créé sans code écrit pour copier les données explicitement. Donc, le compilateur ne crée pas la même chose.Je pensais juste à partager ces connaissances avec vous tous, même si la plupart d'entre vous le savent déjà.
Bravo ... Bon codage !!!
la source