Le mécanisme de copie des objets en C++ se divise en copie superficielle (shallow copy) et copie profonde (deep copy). La différence est particulièrement importante pour les classes avec de la mémoire dynamique.
En C++, de nombreuses structures de données fonctionnent avec de la mémoire dynamique (new/delete). Par défaut, le compilateur génère un constructeur de copie et un opérateur d'assignation qui effectuent une copie au niveau des octets (shallow copy). C'est rapide, mais dangereux si l'objet gère des ressources externes.
La shallow copy ne copie que les adresses des ressources allouées dynamiquement. Lors de la suppression d'un objet, la mémoire sera libérée, et l'autre instance restera avec un pointeur "dangling". En conséquence, des double delete, des fuites de mémoire et des crashes peuvent survenir.
La copie profonde implique la création explicite d'une copie de toutes les ressources dynamiques. Pour cela, il est nécessaire de réaliser manuellement un constructeur de copie et un opérateur d'assignation dans la classe afin de s'assurer que chaque élément soit copié.
Exemple de code pour une classe avec un tableau :
class DynArray { int* data; size_t size; public: DynArray(size_t n) : size(n), data(new int[n]) {} ~DynArray() { delete[] data; } // Constructeur de copie profonde DynArray(const DynArray& other) : size(other.size), data(new int[other.size]) { for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } // Assignation profonde DynArray& operator=(const DynArray& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } return *this; } };
Caractéristiques clés :
Le compilateur génère-t-il toujours correctement le constructeur de copie et l'opérateur d'assignation ?
Réponse :
Faux. Pour les classes avec des ressources dynamiques, la copie par défaut est incorrecte : les deux objets posséderont la même ressource. La deep copy doit être réalisée explicitement lorsqu'il y a des ressources externes.
Faut-il implémenter un destructeur si seul le constructeur de copie/assignation profond est écrit ?
Réponse :
Oui, sinon il y aura une fuite de mémoire : si vous libérez de la mémoire dans le constructeur de copie utilisateur mais que vous ne mettez pas en œuvre un destructeur, la mémoire ne sera libérée par personne lors de la destruction des objets.
std::vector peut-il stocker des pointeurs et pourquoi y a-t-il des fuites possibles lors de sa copie ?
Réponse :
Oui, std::vector peut stocker des pointeurs. Lors de la copie d'un tel std::vector, ce sont les pointeurs eux-mêmes qui sont copiés, pas les objets auxquels ils pointent. C'est une shallow copy : si un deep copy de tout le contenu est nécessaire, il faudra copier manuellement chaque objet et les placer en mémoire de manière indépendante.
Exemple :
std::vector<int*> v1; v1.push_back(new int(42)); std::vector<int*> v2 = v1; // Les pointeurs sont copiés, pas *int
Un programmeur implémente une classe d'enveloppement de tableau sans redéfinir le constructeur de copie/l'opérateur d'assignation. Par conséquent, les deux objets possèdent la même mémoire, la destruction de l'un entraîne un crash lors de l'accès à l'autre.
Avantages :
Inconvénients :
Un développeur implémente une copie profonde : le contenu du tableau est copié, il y a son propre destructeur et un opérateur d'assignation avec protection contre l'auto-assignation.
Avantages :
Inconvénients :