Il meccanismo di copia degli oggetti in C++ si divide in copia superficiale (shallow copy) e copia profonda (deep copy). La differenza è particolarmente importante per le classi con memoria allocata dinamicamente.
In C++, molte strutture dati lavorano con la memoria dinamica (new/delete). Per impostazione predefinita, il compilatore genera un costruttore di copia e un operatore di assegnazione che esegue una copia byte per byte (shallow copy). Questo è veloce, ma pericoloso se l'oggetto gestisce risorse esterne.
La shallow copy copia solo gli indirizzi delle risorse allocate dinamicamente. Quando si elimina un oggetto, la memoria viene liberata, mentre un'altra istanza rimane con un puntatore "dangling". Di conseguenza, si verificano double delete, perdite di memoria e crash.
La copia profonda implica la creazione esplicita di copie di tutte le risorse dinamiche. Per fare ciò, nella classe è necessario implementare personalmente il costruttore di copia e l'operatore di assegnazione, in modo da garantire una copia di ogni elemento.
Esempio di codice per una classe con un array:
class DynArray { int* data; size_t size; public: DynArray(size_t n) : size(n), data(new int[n]) {} ~DynArray() { delete[] data; } // Costruttore di copia profonda 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]; } // Assegnazione profondamente copia 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; } };
Caratteristiche principali:
Il compilatore genera sempre correttamente il costruttore di copia e l'operatore di assegnazione, vero?
Risposta:
Falso. Per le classi con risorse dinamiche, la copia per impostazione predefinita è errata: entrambi gli oggetti possederanno la stessa risorsa. La deep copy deve essere implementata esplicitamente quando si possiedono risorse esterne.
È necessario implementare un distruttore se è stato scritto solo il costruttore/assegnazione a copia profonda?
Risposta:
Sì, altrimenti si verificherà una perdita di memoria: se si libera la memoria nel costruttore di copia utente, ma non si implementa un distruttore, la memoria non verrà liberata durante la distruzione degli oggetti.
Può std::vector contenere puntatori e perché durante la sua copia possono verificarsi perdite?
Risposta:
Sì, std::vector può contenere puntatori. Durante la copia di tale std::vector, vengono copiati solo i puntatori, non gli oggetti a cui puntano. Questa è una shallow copy: se è necessaria una deep copy di tutto il contenuto, sarà necessario copiare manualmente ogni oggetto e allocarli in memoria in modo indipendente.
Esempio:
std::vector<int*> v1; v1.push_back(new int(42)); std::vector<int*> v2 = v1; // Vengono copiati i puntatori, non *int
Un programmatore implementa una classe avvolgente per un array senza ridefinire il costruttore di copia/l'operatore di assegnazione. Di conseguenza, entrambi gli oggetti possiedono la stessa memoria, la distruzione di uno porta a un crash durante l'accesso all'altro.
Vantaggi:
Svantaggi:
Un sviluppatore implementa la copia profonda: si copia il contenuto dell'array, c'è un proprio distruttore e un operatore di assegnazione con protezione contro self-assignment.
Vantaggi:
Svantaggi: