Der Kopiermechanismus für Objekte in C++ unterscheidet sich zwischen flachen Kopien (shallow copy) und tiefen Kopien (deep copy). Der Unterschied ist besonders wichtig für Klassen mit dynamisch zugewiesenem Speicher.
In C++ arbeiten viele Datenstrukturen mit dynamischem Speicher (new/delete). Standardmäßig generiert der Compiler einen Copy-Konstruktor und einen Zuweisungsoperator, der byteweise kopiert (shallow copy). Dies ist schnell, aber riskant, wenn das Objekt externe Ressourcen verwaltet.
Die shallow copy kopiert nur die Adressen der dynamisch zugewiesenen Ressourcen. Wenn ein Objekt gelöscht wird, wird der Speicher freigegeben, und das andere Exemplar bleibt mit einem "hängenden" (dangling) Zeiger zurück. In der Folge können Double Deletes, Speicherlecks und Abstürze auftreten.
Die tiefe Kopie erfordert das explizite Erstellen einer Kopie aller dynamischen Ressourcen. Dafür muss in der Klasse der Copy-Konstruktor und der Zuweisungsoperator selbst implementiert werden, um eine Kopie jedes Elements zu gewährleisten.
Beispielcode für eine Klasse mit einem Array:
class DynArray { int* data; size_t size; public: DynArray(size_t n) : size(n), data(new int[n]) {} ~DynArray() { delete[] data; } // Tiefer Copy-Konstruktor 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]; } // Tiefer Zuweisungsoperator 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; } };
Schlüsselfeatures:
Generiert der Compiler immer korrekt den Copy-Konstruktor und den Zuweisungsoperator?
Antwort:
Nein. Für Klassen mit dynamischen Ressourcen ist die Standardkopie nicht korrekt: Beide Objekte würden einen identischen Ressourcenbesitz haben. Deep copy muss explizit bei Besitz von externen Ressourcen implementiert werden.
Muss ein Destruktor implementiert werden, wenn nur ein deep copy-Konstruktor/Zuweisung geschrieben wird?
Antwort:
Ja, andernfalls tritt ein Speicherleck auf: Wenn der Speicher im benutzerdefinierten Copy-Konstruktor freigegeben wird, der Destruktor aber nicht implementiert wird, bleibt der Speicher bei der Zerstörung der Objekte unfreigegeben.
Kann std::vector Zeiger speichern und warum kann es bei dessen Kopierung zu Lecks kommen?
Antwort:
Ja, std::vector kann problemlos Zeiger speichern. Bei der Kopie eines solchen std::vector werden die Zeiger selbst kopiert und nicht die Objekte, auf die sie zeigen. Das ist eine shallow copy: Wenn eine deep copy des Inhalts erforderlich ist, muss jeder Objekt manuell kopiert und unabhängig im Speicher platziert werden.
Beispiel:
std::vector<int*> v1; v1.push_back(new int(42)); std::vector<int*> v2 = v1; // Die Zeiger werden kopiert, nicht *int
Ein Programmierer implementiert eine Wrapper-Klasse für ein Array, ohne den Copy-Konstruktor/Zuweisungsoperator zu überschreiben. Infolgedessen besitzen beide Objekte denselben Speicher, was beim Löschen eines Objekts zu einem Absturz beim Zugriff auf das andere führt.
Vorteile:
Nachteile:
Ein Entwickler implementiert eine tiefe Kopie: Der Inhalt des Arrays wird kopiert, es gibt einen eigenen Destruktor und einen Zuweisungsoperator mit Schutz gegen Selbstzuweisungen.
Vorteile:
Nachteile: