En C++, lors de la copie d'objets, le mécanisme de copie par membre est utilisé : pour chaque membre de l'objet, son propre opérateur de copie est appelé. Pour les types intégrés, cela est sans danger, mais pour les ressources dynamiques, un problème survient : seuls les pointeurs sont copiés, mais pas les données elles-mêmes.
Si un objet contient un pointeur vers de la mémoire allouée dans le tas, alors après la copie de deux objets, ils pointeront vers le même espace mémoire. Ainsi, à la destruction d'un objet, la mémoire sera libérée et le pointeur de l'autre deviendra invalide ("pointeur sauvage"). Cela entraîne des erreurs d'exécution et des fuites de mémoire.
Pour que la copie soit indépendante, un deep copy est nécessaire — une copie au niveau des octets et l'allocation d'un tampon propre. Cela s'implémente en écrivant un constructeur de copie et un opérateur d'assignation personnalisés.
Exemple de code :
class MyString { char* data; public: MyString(const char* s) { data = new char[strlen(s)+1]; strcpy(data, s); } // Constructeur de copie profond MyString(const MyString& src) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } // Assignation de copie profonde MyString& operator=(const MyString& src) { if (this != &src) { delete[] data; data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } return *this; } ~MyString() { delete[] data; } };
Caractéristiques clés :
Pourquoi un destructeur personnalisé est-il nécessaire si la classe ne contient qu'un pointeur, mais aucune mémoire n'est allouée ?
Le destructeur n'est nécessaire que si vous avez explicitement alloué de la mémoire (ou une autre ressource) dans la classe. Si le pointeur n'alloue pas de mémoire, le destructeur par défaut est suffisant.
Que se passera-t-il si l'opérateur = n'est pas implémenté dans une classe avec mémoire dynamique, mais le constructeur de copie est déclaré ?
Si vous avez défini manuellement le constructeur de copie, le compilateur n'implementera pas automatiquement l'opérateur = ; il sera soit déclaré implicitement, soit le compilateur générera une erreur/un avertissement (selon le standard). Cela conduira à un comportement mal défini lors de l'assignation : une copie par membre se produira et entraînera un double free ou une fuite.
Exemple de code :
MyString a("hi"); MyString b = a; // Ok : votre constructeur de copie MyString c("bye"); c = a; // Problème ! Si l'opérateur = n'est pas implémenté manuellement, ce sera une shallow copy
Pourquoi l'assignation à soi-même est-elle dangereuse lors de l'implémentation manuelle de l'opérateur = ?
Si vous partagez des ressources sans vérifier this!=&rhs, alors en s'assignant à soi-même, delete[] data sera exécuté, puis la copie d'un tableau déjà détruit, ce qui entraînera un segfault. Auto-protection : vérifiez toujours l'auto-assignation.
if (this != &rhs) { ... }
Un développeur copie un objet d'une classe avec un pointeur intégré, sans réaliser de deep copy. Après la copie, plusieurs objets partagent le même espace mémoire. Deux destructeurs provoquent un double delete, la programme plante.
Avantages :
Inconvénients :
Un développeur implémente correctement le constructeur de copie, l'opérateur d'assignation et le destructeur. Chaque objet possède sa propre mémoire.
Avantages :
Inconvénients :