Wird geladen...

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:

→ 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.

Ähnliche Artikel