Destruktoren
Ein Destruktor ist eine spezielle Memberfunktion, die aufgerufen wird, wenn das Lebensende eines Objekts erreicht wird. Der Zweck des Destruktors ist es, die Ressourcen freizugeben, die das Objekt während seines Lebens erworben haben mag.
|
Ein Destruktor kann keine Coroutine sein. |
(seit C++20) |
[edit] Syntax
Destruktoren(bis C++20)Potenzielle Destruktoren(seit C++20) werden mit Member-Funktionsdeklaratoren der folgenden Form deklariert
klassenname-mit-tildesymbol ( parameterliste (optional) ) except (optional) attr (optional) |
|||||||||
| klassenname-mit-tildesymbol | - | ein Ausdruck eines Bezeichners, gefolgt von einer Liste von Attributen, und(seit C++11) optional eingeschlossen von einem Paar Klammern | ||||||
| parameter-liste | - | Parameterliste (muss entweder leer oder void sein) | ||||||
| except | - |
| ||||||
| attr | - | (seit C++11) eine Liste von Attributen |
Die einzigen Spezifizierer, die in den Deklarationsspezifizierern einer potenziellen(seit C++20) Destruktor-Deklaration erlaubt sind, sind constexpr,(seit C++11) friend, inline und virtual (insbesondere ist kein Rückgabetyp erlaubt).
Der Bezeichnerausdruck von klassenname-mit-tildesymbol muss eine der folgenden Formen haben
- In einer Member-Deklaration, die zur Member-Spezifikation einer Klasse oder Klassenvorlage gehört, aber keine Friend-Deklaration ist
- Bei Klassen ist der Bezeichnerausdruck ein ~ gefolgt vom eingeschleusten Klassennamen der unmittelbar umschließenden Klasse.
- Bei Klassenvorlagen ist der Bezeichnerausdruck ein ~ gefolgt von einem Klassennamen, der die aktuelle Instanziierung benennt(bis C++20)dem eingeschleusten Klassennamen(seit C++20) der unmittelbar umschließenden Klassenvorlage.
- Andernfalls ist der Bezeichnerausdruck ein qualifizierter Bezeichner, dessen terminaler nicht-qualifizierter Bezeichner ein ~ gefolgt vom eingeschleusten Klassennamen der durch die nicht-terminalen Teile des qualifizierten Bezeichners nominierten Klasse ist.
[edit] Erklärung
Der Destruktor wird implizit aufgerufen, wann immer das Lebensende eines Objekts erreicht wird, einschließlich
- Programmabbruch für Objekte mit statischer Speicherdauer
|
(seit C++11) |
- Ende des Gültigkeitsbereichs für Objekte mit automatischer Speicherdauer und für temporäre Objekte, deren Lebensdauer durch Bindung an eine Referenz verlängert wurde
- delete-Ausdruck für Objekte mit dynamischer Speicherdauer
- Ende des vollständigen Ausdrucks für namenlose temporäre Objekte
- Stack-Unwinding für Objekte mit automatischer Speicherdauer, wenn eine Ausnahme ihren Block ungefangen verlässt.
Der Destruktor kann auch explizit aufgerufen werden.
Potenzieller DestruktorEine Klasse kann einen oder mehrere potenzielle Destruktoren haben, von denen einer als Destruktor für die Klasse ausgewählt wird. Um zu bestimmen, welcher potenzielle Destruktor der Destruktor ist, wird am Ende der Definition der Klasse Überladungsauflösung unter den potenziellen Destruktoren, die in der Klasse mit einer leeren Argumentliste deklariert wurden, durchgeführt. Wenn die Überladungsauflösung fehlschlägt, ist das Programm ill-formed. Die Destruktorauswahl verwendet den ausgewählten Destruktor nicht per odr-use, und der ausgewählte Destruktor kann gelöscht sein. Alle potenziellen Destruktoren sind spezielle Memberfunktionen. Wenn für die Klasse Führen Sie diesen Code aus #include <cstdio> #include <type_traits> template<typename T> struct A { ~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); } ~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); } ~A() { std::puts("~A, T is anything else"); } }; int main() { A<int> a; A<int*> b; A<float> c; } Ausgabe ~A, T is anything else ~A, T is a pointer ~A, T is integral |
(seit C++20) |
[edit] Potenziell aufzurufender Destruktor
Der Destruktor für die Klasse T wird in den folgenden Situationen *potenziell aufgerufen*
- Er wird explizit oder implizit aufgerufen.
- Ein new-Ausdruck erstellt ein Array von Objekten vom Typ
T. - Das Ergebnisobjekt einer return-Anweisung hat den Typ
T. - Ein Array wird mit Aggregatinitialisierung initialisiert und sein Elementtyp ist
T. - Ein Klassenobjekt wird mit Aggregatinitialisierung initialisiert und hat ein Member vom Typ
T, wobeiTkein Typ einer anonymen Union ist. - Ein potenziell konstruierbares Unterobjekt hat den Typ
Tin einem nicht-delegierenden(seit C++11) Konstruktor. - Ein Ausnahmeobjekt vom Typ
Twird konstruiert.
Wenn ein potenziell aufzurufender Destruktor gelöscht oder(seit C++11) vom Kontext des Aufrufs aus nicht zugänglich ist, ist das Programm ill-formed.
[edit] Implizit deklarierter Destruktor
Wenn für einen Klassentyp kein benutzerdefinierter potenzieller(seit C++20) Destruktor bereitgestellt wird, deklariert der Compiler immer einen Destruktor als inline public Member seiner Klasse.
Wie bei jeder implizit deklarierten speziellen Memberfunktion ist die Ausnahmespezifikation des implizit deklarierten Destruktors nicht-werfend, es sei denn, der Destruktor einer potenziell konstruierbaren Basisklasse oder eines potenziell konstruierbaren Members ist potenziell werfend(seit C++17)die implizite Definition würde direkt eine Funktion mit einer anderen Ausnahmespezifikation aufrufen(bis C++17). In der Praxis sind implizite Destruktoren noexcept, es sei denn, die Klasse ist durch eine Basisklasse oder ein Member "vergiftet", deren Destruktor noexcept(false) ist.
[edit] Implizit definierter Destruktor
Wenn ein implizit deklarierter Destruktor nicht gelöscht ist, wird er implizit definiert (d.h. ein Funktionskörper wird generiert und kompiliert), wenn er ODR-used wird. Dieser implizit definierte Destruktor hat einen leeren Körper.
|
Wenn dies die Anforderungen an einen constexpr-Destruktor(bis C++23)constexpr-Funktion(seit C++23) erfüllt, ist der generierte Destruktor constexpr. |
(seit C++20) |
Gelöschter DestruktorDer implizit deklarierte oder explizit standardmäßig definierte Destruktor für die Klasse
|
(seit C++11) |
[edit] Trivialer Destruktor
Der Destruktor für die Klasse T ist trivial, wenn alle folgenden Bedingungen erfüllt sind
- Der Destruktor ist implizit deklariert(bis C++11)nicht benutzerdefiniert(seit C++11).
- Der Destruktor ist nicht virtuell.
- Alle direkten Basisklassen haben triviale Destruktoren.
|
(bis C++26) |
|
(seit C++26) |
Ein trivialer Destruktor ist ein Destruktor, der keine Aktion ausführt. Objekte mit trivialen Destruktoren benötigen keinen delete-Ausdruck und können durch einfache Deallokation ihres Speichers entsorgt werden. Alle mit der C-Sprache kompatiblen Datentypen (POD-Typen) sind trivial destruierbar.
[edit] Destruktionssequenz
Sowohl bei benutzerdefinierten als auch bei implizit definierten Destruktoren ruft der Compiler nach der Ausführung des Rumpfes des Destruktors und der Zerstörung aller darin zugewiesenen automatischen Objekte die Destruktoren für alle nicht-statischen, nicht-varianten Datenmember der Klasse in umgekehrter Deklarationsreihenfolge auf, dann ruft er die Destruktoren aller direkten, nicht-virtuellen Basisklassen in umgekehrter Konstruktionsreihenfolge auf (die wiederum die Destruktoren ihrer Member und ihrer Basisklassen aufrufen usw.), und dann ruft er, wenn dieses Objekt die am weitesten abgeleitete Klasse ist, die Destruktoren aller virtuellen Basen auf.
Selbst wenn der Destruktor direkt aufgerufen wird (z.B. obj.~Foo();), kehrt die return-Anweisung in ~Foo() nicht sofort zur aufrufenden Funktion zurück: Sie ruft zuerst alle diese Member- und Basisdestruktoren auf.
[edit] Virtuelle Destruktoren
Das Löschen eines Objekts über einen Zeiger auf die Basisklasse führt zu undefiniertem Verhalten, es sei denn, der Destruktor in der Basisklasse ist virtuell.
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // safe
Eine allgemeine Richtlinie besagt, dass ein Destruktor für eine Basisklasse entweder öffentlich und virtuell oder geschützt und nicht-virtuell sein muss.
[edit] Rein virtuelle Destruktoren
Ein potenzieller(seit C++20) Destruktor kann als rein virtuell deklariert werden, zum Beispiel in einer Basisklasse, die abstrakt gemacht werden muss, aber keine anderen geeigneten Funktionen hat, die rein virtuell deklariert werden könnten. Ein rein virtueller Destruktor muss eine Definition haben, da alle Basisklassen-Destruktoren immer aufgerufen werden, wenn die abgeleitete Klasse zerstört wird.
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // compiler error Derived obj; // OK
[edit] Ausnahmen
Wie jede andere Funktion kann auch ein Destruktor durch das Werfen einer Ausnahme enden (dies erfordert normalerweise, dass er explizit als noexcept(false) deklariert wird)(seit C++11). Wenn dieser Destruktor jedoch während des Stack-Unwinding aufgerufen wird, wird stattdessen std::terminate aufgerufen.
Obwohl std::uncaught_exceptions manchmal verwendet werden kann, um ein laufendes Stack-Unwinding zu erkennen, gilt es im Allgemeinen als schlechte Praxis, zuzulassen, dass ein Destruktor durch das Werfen einer Ausnahme endet. Diese Funktionalität wird jedoch von einigen Bibliotheken verwendet, wie z. B. SOCI und Galera 3, die sich auf die Fähigkeit von Destruktoren namenlosen temporären Objekte verlassen, Ausnahmen am Ende des vollständigen Ausdrucks zu werfen, der das temporäre Objekt konstruiert.
std::experimental::scope_success in Library fundamental TS v3 kann einen potenziell werfenden Destruktor haben, der eine Ausnahme wirft, wenn der Gültigkeitsbereich normal verlassen wird und die Exit-Funktion eine Ausnahme wirft.
[edit] Hinweise
Das direkte Aufrufen eines Destruktors für ein gewöhnliches Objekt, wie eine lokale Variable, führt zu undefiniertem Verhalten, wenn der Destruktor am Ende des Gültigkeitsbereichs erneut aufgerufen wird.
In generischen Kontexten kann die Destruktoraufrufsyntax mit einem Objekt eines Nicht-Klassentyps verwendet werden; dies wird als Pseudo-Destruktoraufruf bezeichnet: siehe Member-Zugriffsoperatoren.
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_trivial_union |
202502L |
(C++26) | Lockerung der trivialitätsanforderungen für spezielle Memberfunktionen von Unions |
[edit] Beispiel
#include <iostream> struct A { int i; A(int num) : i(num) { std::cout << "ctor a" << i << '\n'; } (~A)() // but usually ~A() { std::cout << "dtor a" << i << '\n'; } }; A a0(0); int main() { A a1(1); A* p; { // nested scope A a2(2); p = new A(3); } // a2 out of scope delete p; // calls the destructor of a3 }
Ausgabe
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
[edit] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 193 | C++98 | ob automatische Objekte in einem Destruktor vor oder nach der Zerstörung der Basis- und Member-Unterobjekte der Klasse zerstört wurden, war nicht spezifiziert |
sie werden zerstört vor der Zerstörung dieser Unterobjekte |
| CWG 344 | C++98 | die Deklaratorsyntax von Destruktoren war fehlerhaft (hatte das gleiche Problem wie CWG-Issue 194 und CWG-Issue 263 |
die Syntax wurde zu einer spezialisierten Funktionsdeklaratorsyntax geändert |
| CWG 1241 | C++98 | statische Member könnten zerstört werden kurz nach der Ausführung des Destruktors |
zerstöre nur nicht- statische Member |
| CWG 1353 | C++98 | die Bedingungen, unter denen implizit deklarierte Destruktoren undefiniert sind, berücksichtigten keine mehrdimensionalen Array-Typen |
diese Typen berücksichtigen |
| CWG 1435 | C++98 | die Bedeutung von "Klassenname" in der Deklaratorsyntax von Destruktoren war unklar |
die Syntax wurde zu einer spezialisierten Funktionsdeklaratorsyntax geändert |
| CWG 2180 | C++98 | der Destruktor einer Klasse, die keine am weitesten abgeleitete Klasse ist würde die Destruktoren ihrer virtuellen direkten Basisklassen aufrufen |
er ruft diese Destruktoren nicht auf |
| CWG 2807 | C++20 | die Deklarationsspezifizierer konnten consteval enthalten | verboten |