Copy-Konstruktor und Zuweisungsoperator in C++
Lernen Sie Copy-Konstruktoren und Zuweisungsoperatoren in C++, einschließlich Kopiersemantik und Ressourcenverwaltung.
In C++ kommen beim Zuweisen oder Kopieren von Objekten zwei spezielle Mechanismen zum Einsatz: der Kopierkonstruktor (Copy Constructor) und der Zuweisungsoperator (=). Beide übertragen Daten zwischen Objekten, unterscheiden sich jedoch in Zeitpunkt und Zweck. In diesem Artikel lernen wir ihre Unterschiede, Anwendungsbeispiele und das Konzept des Deep Copy (tiefen Kopierens) kennen.
1. Was ist ein Copy Constructor?
Der Kopierkonstruktor ist ein spezieller Konstruktor, der aufgerufen wird, wenn ein neues Objekt als Kopie eines bestehenden erstellt wird.
class Book {
private:
string *title;
public:
Book(string t) {
title = new string(t);
cout << "Konstruktor aufgerufen." << endl;
}
// Copy Constructor
Book(const Book &other) {
title = new string(*other.title); // Tiefe Kopie
cout << "Copy Constructor aufgerufen." << endl;
}
~Book() {
delete title;
cout << "Destruktor aufgerufen." << endl;
}
void Show() const { cout << "Buch: " << *title << endl; }
};
Verwendung:
int main() {
Book b1("C++ Programmierung");
Book b2 = b1; // Copy Constructor wird aufgerufen
b1.Show();
b2.Show();
}
Ausgabe:
Konstruktor aufgerufen.
Copy Constructor aufgerufen.
Buch: C++ Programmierung
Buch: C++ Programmierung
Destruktor aufgerufen.
Destruktor aufgerufen.
In diesem Fall besitzt jedes Objekt seinen eigenen separaten Speicherbereich (tiefe Kopie). Wenn nur die Adresse kopiert wird (shallow copy), teilen sich beide Objekte denselben Speicher, was beim Aufruf des Destruktors zu einem „Double Delete“-Fehler führt.
2. Was ist der Zuweisungsoperator (=)?
Der Zuweisungsoperator wird verwendet, um den Inhalt eines Objekts auf ein bereits existierendes anderes Objekt zu übertragen. Das heißt, das Objekt existiert bereits; nur seine Daten werden ersetzt.
class Book {
private:
string *title;
public:
Book(string t) {
title = new string(t);
cout << "Konstruktor aufgerufen." << endl;
}
// Zuweisungsoperator
Book& operator=(const Book &other) {
cout << "Zuweisungsoperator aufgerufen." << endl;
// Selbstzuweisung prüfen
if (this == &other)
return *this;
// Alten Speicher löschen
delete title;
// Neue Kopie erstellen
title = new string(*other.title);
return *this;
}
~Book() {
delete title;
cout << "Destruktor aufgerufen." << endl;
}
void Show() const { cout << "Buch: " << *title << endl; }
};
Verwendung:
int main() {
Book b1("Modernes C++");
Book b2("Python Handbuch");
b2 = b1; // Zuweisungsoperator wird aufgerufen
b1.Show();
b2.Show();
}
Ausgabe:
Konstruktor aufgerufen.
Konstruktor aufgerufen.
Zuweisungsoperator aufgerufen.
Buch: Modernes C++
Buch: Modernes C++
Destruktor aufgerufen.
Destruktor aufgerufen.
Hier haben beide Objekte verschiedene Speicheradressen, aber identische Inhalte. Die alten Daten wurden sicher gelöscht und durch neue ersetzt.
3. Problem der Shallow Copy (oberflächliche Kopie)
Wenn kein Kopierkonstruktor oder Zuweisungsoperator definiert ist, erstellt der C++-Compiler automatisch eine „oberflächliche Kopie (shallow copy)“. Dabei wird nur die Adresse kopiert, nicht die tatsächlichen Daten.
class ShallowBook {
public:
string *title;
ShallowBook(string t) {
title = new string(t);
}
~ShallowBook() {
delete title;
}
};
int main() {
ShallowBook b1("C++ Primer");
ShallowBook b2 = b1; // Automatische Shallow Copy
cout << *b1.title << endl;
cout << *b2.title << endl; // Gleicher Speicherbereich!
} // Destruktor ruft zweimal delete auf → Absturz
Daher müssen in Klassen, die dynamischen Speicher verwenden, der Kopierkonstruktor und der Zuweisungsoperator manuell definiert werden.
4. Sichere Verwendung mit Deep Copy (Tiefenkopie)
Eine Tiefenkopie kopiert die tatsächlichen Daten in einen neuen Speicherbereich. Dadurch verwaltet jedes Objekt seinen eigenen Speicher unabhängig voneinander.
class DeepBook {
private:
string *title;
public:
DeepBook(string t) {
title = new string(t);
}
// Tiefenkopie-Konstruktor
DeepBook(const DeepBook &other) {
title = new string(*other.title);
}
// Tiefenkopie-Zuweisungsoperator
DeepBook& operator=(const DeepBook &other) {
if (this == &other) return *this;
delete title;
title = new string(*other.title);
return *this;
}
~DeepBook() {
delete title;
}
void Show() const { cout << *title << endl; }
};
int main() {
DeepBook b1("Ich lerne Deep Copy");
DeepBook b2 = b1; // Copy-Konstruktor
DeepBook b3("Leer");
b3 = b1; // Zuweisungsoperator
b1.Show();
b2.Show();
b3.Show();
}
Jedes Objekt verwaltet seinen eigenen Speicher; der Destruktor wird für jedes Objekt sicher aufgerufen.
5. Die Regel der Drei (Rule of Three)
Wenn eine Klasse:
- Einen Destruktor definiert,
- Einen Copy-Konstruktor definiert,
- Einen Zuweisungsoperator definiert,
→ Dann sollten alle drei gemeinsam definiert werden. Wenn einer definiert und die anderen weggelassen werden, generiert der Compiler automatisch Standardversionen, was zu unerwartetem Verhalten führen kann.
Seit C++11: wurde das Konzept der „Rule of Five“ eingeführt, das zusätzlich den Move-Konstruktor und den Move-Zuweisungsoperator einschließt.
6. Praktisches Beispiel: Kopieren und Zuweisen von Büchern
Das folgende Beispiel zeigt, wie Copy-Konstruktor und Zuweisungsoperator zusammenarbeiten.
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
string *title;
public:
Book(string t) {
title = new string(t);
cout << "Konstruktor: " << *title << endl;
}
// Copy-Konstruktor
Book(const Book &other) {
title = new string(*other.title);
cout << "Copy-Konstruktor: " << *title << endl;
}
// Zuweisungsoperator
Book& operator=(const Book &other) {
cout << "Zuweisungsoperator: " << *other.title << endl;
if (this == &other) return *this;
delete title;
title = new string(*other.title);
return *this;
}
~Book() {
cout << "Destruktor: " << *title << endl;
delete title;
}
void Show() const { cout << *title << endl; }
};
int main() {
Book b1("C++ Grundlagen");
Book b2 = b1; // Copy-Konstruktor
Book b3("Temporär");
b3 = b1; // Zuweisungsoperator
cout << "----- Ausgabe -----" << endl;
b1.Show();
b2.Show();
b3.Show();
}
Ausgabe:
Konstruktor: C++ Grundlagen
Copy-Konstruktor: C++ Grundlagen
Konstruktor: Temporär
Zuweisungsoperator: C++ Grundlagen
----- Ausgabe -----
C++ Grundlagen
C++ Grundlagen
C++ Grundlagen
Destruktor: C++ Grundlagen
Destruktor: C++ Grundlagen
Destruktor: C++ Grundlagen
Damit werden sowohl das Kopieren als auch die Zuweisung von Objekten sicher und kontrolliert durchgeführt.
7. TL;DR
- Copy-Konstruktor → Erstellt ein neues Objekt als Kopie eines bestehenden.
- Zuweisungsoperator → Weist den Inhalt eines bestehenden Objekts einem anderen zu.
- Ohne Tiefenkopie kann ein „Double Delete“-Fehler auftreten.
- Rule of Three → Destruktor, Copy-Konstruktor und Zuweisungsoperator sollten alle definiert werden.
- Rule of Five (C++11) → Enthält zusätzlich Move-Konstruktor und Move-Zuweisungsoperator.
- Alle Beispiele können sicher in Visual Studio 2022 oder GCC 11+ ausgeführt werden.