Konstruktoren und Destruktoren in C++
Lernen Sie Konstruktoren und Destruktoren in C++, um Objektlebenszyklen und Ressourcenverwaltung zu verstehen.
In C++ besitzt jede Klasse spezielle Methoden, die beim Erstellen und Zerstören eines Objekts aufgerufen werden: den Konstruktor und den Destruktor. Der Konstruktor bereitet das Objekt auf die erste Verwendung vor, während der Destruktor den Speicher bereinigt. Diese Struktur bildet die Grundlage des RAII (Resource Acquisition Is Initialization)-Prinzips.
1. Was ist ein Konstruktor?
Ein Konstruktor ist eine spezielle Methode, die automatisch aufgerufen wird, wenn ein Objekt einer Klasse erstellt wird. Sein Zweck besteht darin, Variablen zu initialisieren und notwendige Ressourcen vorzubereiten.
- Sein Name muss mit dem Klassennamen identisch sein.
- Er hat keinen Rückgabewert (keinen Return-Typ).
- Er kann überladen werden (mehrere Konstruktoren in derselben Klasse).
class Book {
private:
string title;
int pages;
public:
// Standardkonstruktor
Book() {
title = "Unbekannt";
pages = 0;
cout << "Standardkonstruktor aufgerufen." << endl;
}
// Konstruktor mit Parametern
Book(string t, int p) {
title = t;
pages = p;
cout << "Parametrisierter Konstruktor aufgerufen." << endl;
}
void Display() {
cout << "Buch: " << title << " (" << pages << " Seiten)" << endl;
}
};
Der Konstruktor wird automatisch aufgerufen, wenn ein Objekt erstellt wird:
int main() {
Book b1;
Book b2("C++ Programmierung", 900);
b1.Display();
b2.Display();
}
2. Was ist ein Destruktor?
Ein Destruktor ist eine spezielle Methode, die aufgerufen wird, wenn die Lebensdauer eines Objekts endet. Er wird verwendet, um Aufräumarbeiten durchzuführen, wie das Freigeben von Speicher oder das Schließen von Dateien.
- Er hat denselben Namen wie die Klasse, wird jedoch mit
~(Tilde) eingeleitet. - Er nimmt keine Parameter an und kann nicht überladen werden.
class Book {
public:
Book() {
cout << "Konstruktor ausgeführt." << endl;
}
~Book() {
cout << "Destruktor ausgeführt." << endl;
}
};
int main() {
Book b;
} // Am Ende von main() wird der Destruktor automatisch aufgerufen
Ausgabe:
Konstruktor ausgeführt.
Destruktor ausgeführt.
Da der Destruktor automatisch aufgerufen wird, sollte er nicht manuell aufgerufen werden.
3. Beispiel Book: Erweiterter Konstruktor und Destruktor
Machen wir nun die Klasse Book realistischer. Sie enthält dynamisch zugewiesene Daten (zum Beispiel eine Buchbeschreibung). So wird deutlich, warum Destruktoren wichtig sind.
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
string title;
int pages;
string *description; // Dynamischer Speicher
public:
// Konstruktor
Book(string t, int p, string d)
: title(t), pages(p) {
description = new string(d);
cout << "Book-Konstruktor: " << title << endl;
}
// Destruktor
~Book() {
cout << "Book-Destruktor: " << title << endl;
delete description; // Dynamischer Speicher wird freigegeben
}
void Display() const {
cout << "Buch: " << title << " (" << pages << " Seiten)" << endl;
cout << "Beschreibung: " << *description << endl;
}
};
int main() {
Book b1("1984", 328, "Ein dystopischer Roman.");
b1.Display();
}
Ausgabe:
Book-Konstruktor: 1984
Buch: 1984 (328 Seiten)
Beschreibung: Ein dystopischer Roman.
Book-Destruktor: 1984
Der Destruktor wird automatisch aufgerufen, wenn das Objekt am Ende von main() zerstört wird,
und gibt den mit new reservierten Speicher wieder frei.
4. Konstruktor-Überladung
In derselben Klasse können mehrere Konstruktoren definiert werden. Der passende Konstruktor wird anhand der Anzahl und des Typs der Parameter aufgerufen.
class Book {
private:
string title;
int pages;
public:
Book() : title("Unbekannt"), pages(0) {}
Book(string t) : title(t), pages(100) {}
Book(string t, int p) : title(t), pages(p) {}
void Display() {
cout << "Buch: " << title << " (" << pages << " Seiten)" << endl;
}
};
int main() {
Book b1;
Book b2("C++ 101");
Book b3("Design Patterns", 450);
b1.Display();
b2.Display();
b3.Display();
}
5. Copy Constructor (Kopierkonstruktor)
Standardmäßig erstellt C++ automatisch einen Kopierkonstruktor, aber wenn dynamischer Speicher verwendet wird, sollte ein eigener Copy Constructor definiert werden.
class Book {
private:
string title;
string *desc;
public:
Book(string t, string d) {
title = t;
desc = new string(d);
cout << "Konstruktor: " << title << endl;
}
// Kopierkonstruktor
Book(const Book &other) {
title = other.title;
desc = new string(*other.desc); // tiefe Kopie
cout << "Copy Constructor: " << title << endl;
}
~Book() {
delete desc;
cout << "Destruktor: " << title << endl;
}
void Display() {
cout << title << " - " << *desc << endl;
}
};
int main() {
Book b1("C++ Primer", "Einsteigerhandbuch");
Book b2 = b1; // Copy Constructor wird aufgerufen
b2.Display();
}
Wenn keine tiefe Kopie (deep copy) durchgeführt wird, teilen sich beide Objekte denselben Speicherbereich, was zu einem „doppelten Delete“-Fehler führt, wenn der Destruktor ausgeführt wird.
6. Buchsystem – Lebenszyklusverfolgung
Das folgende Beispiel zeigt die Reihenfolge der Erstellung und Zerstörung eines Buchobjekts. Es ist ideal, um den tatsächlichen Lebenszyklus von Konstruktoren und Destruktoren zu verstehen.
#include <iostream>
#include <string>
using namespace std;
class Author {
public:
Author(string n) { cout << "Autor erstellt: " << n << endl; }
~Author() { cout << "Autor zerstört." << endl; }
};
class Book {
private:
string title;
Author author;
public:
Book(string t, string a) : title(t), author(a) {
cout << "Buch erstellt: " << title << endl;
}
~Book() {
cout << "Buch zerstört: " << title << endl;
}
};
int main() {
Book b("Farm der Tiere", "George Orwell");
}
Ausgabe:
Autor erstellt: George Orwell
Buch erstellt: Farm der Tiere
Buch zerstört: Farm der Tiere
Autor zerstört.
Wie man sieht, wird zuerst der Author-Konstruktor aufgerufen,
gefolgt vom Book-Konstruktor.
Bei der Zerstörung geschieht das Gegenteil (zuerst Book, dann Author).
Dies zeigt die Lebenszyklusreihenfolge bei Kompositionsbeziehungen in C++.
7. TL;DR
- Konstruktor → Wird automatisch aufgerufen, wenn ein Objekt erstellt wird.
- Destruktor → Wird automatisch aufgerufen, wenn der Lebenszyklus eines Objekts endet.
- Konstruktoren können überladen werden, Destruktoren nicht.
- Wenn dynamischer Speicher verwendet wird, muss der Destruktor
deleteaufrufen. - Der Copy Constructor sollte eine tiefe Kopie durchführen.
- Erstellungsreihenfolge: zuerst Komponenten, dann die Klasse; Zerstörungsreihenfolge: zuerst die Klasse, dann die Komponenten.
- Alle Beispiele können in Visual Studio 2022 oder GCC 11+ ausgeführt werden.