Destructors
Ein Destruktor ist eine spezielle Memberfunktion , die aufgerufen wird, wenn die Lebensdauer eines Objekts endet. Der Zweck des Destruktors ist es, die Ressourcen freizugeben, die das Objekt während seiner Lebensdauer möglicherweise erworben hat.
|
Ein Destruktor kann keine Coroutine sein. |
(seit C++20) |
Syntax
Destruktoren (bis C++20) Prospektive Destruktoren (seit C++20) werden mit Element- Funktionsdeklaratoren der folgenden Form deklariert:
class-name-with-tilde
(
parameter-list
(optional)
)
except
(optional)
attr
(optional)
|
|||||||||
| class-name-with-tilde | - | ein Identifikatorausdruck , möglicherweise gefolgt von einer Liste von Attributen , und (seit C++11) möglicherweise umschlossen von einem Klammerpaar | ||||||
| parameter-list | - | Parameterliste (muss entweder leer oder void sein) | ||||||
| except | - |
|
||||||
| attr | - | (seit C++11) eine Liste von Attributen |
Die einzigen Spezifizierer, die in den
Deklarationsspezifizierern
einer
prospektiven
(seit C++20)
Destruktordeklaration erlaubt sind, sind
constexpr
,
(seit C++11)
friend
,
inline
und
virtual
(insbesondere ist kein Rückgabetyp erlaubt).
Der Bezeichnerausdruck von class-name-with-tilde muss eine der folgenden Formen haben:
- In einer Member-Deklaration, die zur Member-Spezifikation einer Klasse oder eines Klassentemplates gehört, aber keine Friend-Deklaration ist:
-
- Für Klassen ist der Identifikatorausdruck ~ gefolgt vom injected-class-name der unmittelbar umschließenden Klasse.
- Für Klassentemplates ist der Identifikatorausdruck ~ gefolgt von einem Klassennamen, der die current instantiation benennt (bis C++20) dem injected-class-name (seit C++20) des unmittelbar umschließenden Klassentemplates.
- Andernfalls ist der Bezeichnerausdruck ein qualifizierter Bezeichner, dessen terminaler unqualifizierter Bezeichner ~ gefolgt vom injizierten Klassennamen der Klasse ist, die durch die nicht-terminalen Teile des qualifizierten Bezeichners bezeichnet wird.
Erklärung
Der Destruktor wird implizit aufgerufen, wenn die Lebensdauer eines Objekts endet, was einschließt
- Programmbeendigung , für Objekte mit statischer Speicherdauer
|
(since C++11) |
- Ende des Gültigkeitsbereichs, für Objekte mit automatischer Speicherdauer und für Temporärwerte, deren Lebensdauer durch Bindung an eine Referenz verlängert wurde
- delete expression , für Objekte mit dynamischer Speicherdauer
- Ende des vollständigen expression , für namenlose Temporärwerte
- stack unwinding , für Objekte mit automatischer Speicherdauer, wenn eine Ausnahme ihren Block ungefangen verlässt
Der Destruktor kann auch explizit aufgerufen werden.
Prospektiver DestruktorEine Klasse kann einen oder mehrere prospektive Destruktoren besitzen, von denen einer als Destruktor für die Klasse ausgewählt wird. Um zu bestimmen, welcher prospektive Destruktor der Destruktor ist, wird am Ende der Definition der Klasse Überladungsauflösung unter den in der Klasse deklarierten prospektiven Destruktoren mit einer leeren Argumentliste durchgeführt. Schlägt die Überladungsauflösung fehl, ist das Programm fehlerhaft. Die Destruktorauswahl odr-verwendet den ausgewählten Destruktor nicht, und der ausgewählte Destruktor kann gelöscht sein.
Alle prospektiven Destruktoren sind spezielle Elementfunktionen. Wenn kein benutzerdeklarierter prospektiver Destruktor für die Klasse
Führe 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) |
Potenziell aufgerufener Destruktor
Der Destruktor für die Klasse
T
wird
potenziell aufgerufen
in den folgenden Situationen:
- Es wird explizit oder implizit aufgerufen.
-
Ein
new
expression
erzeugt ein Array von Objekten des Typs
T. -
Das Ergebnisobjekt einer
return
statement
ist vom Typ
T. -
Ein Array unterliegt der
aggregate initialization
, und sein Elementtyp ist
T. -
Ein Klassenobjekt unterliegt der Aggregate-Initialisierung und hat ein Mitglied vom Typ
T, wobeiTkein anonymous union -Typ ist. -
Ein
potentially constructed subobject
ist vom Typ
Tin einem non- delegating (since C++11) constructor. -
Ein
exception object
vom Typ
Twird konstruiert.
Wenn ein potenziell aufgerufener Destruktor gelöscht oder (since C++11) vom Kontext des Aufrufs nicht zugreifbar ist, ist das Programm fehlerhaft.
Implizit deklarierter Destruktor
Wenn kein benutzerdeklarierter prospektiver (seit C++20) Destruktor für einen Klassentyp bereitgestellt wird, deklariert der Compiler immer einen Destruktor als inline public Mitglied seiner Klasse.
Wie bei jeder implizit deklarierten speziellen Elementfunktion ist die Ausnahmespezifikation des implizit deklarierten Destruktors nicht-werfend, es sei denn der Destruktor einer potenziell konstruierten Basis oder Member-Klasse 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 wird durch eine Basis oder Member-Klasse "vergiftet", deren Destruktor noexcept ( false ) ist.
Implizit definierter Destruktor
Wenn ein implizit deklarierter Destruktor nicht gelöscht wird, wird er implizit definiert (d.h. ein Funktionskörper wird generiert und kompiliert) vom Compiler, wenn er ODR-used wird. Dieser implizit definierte Destruktor hat einen leeren Körper.
|
Wenn dies die Anforderungen eines constexpr Destruktors (bis C++23) constexpr Funktion (seit C++23) erfüllt, ist der generierte Destruktor constexpr . |
(seit C++20) |
Gelöschter Destruktor
Der implizit deklarierte oder explizit default-definierte Destruktor für Klasse
|
(seit C++11) |
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 benutzerbereitgestellt (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 einfaches Freigeben ihres Speichers entsorgt werden. Alle mit der C-Sprache kompatiblen Datentypen (POD-Typen) sind trivial destruierbar.
Zerstörungssequenz
Für sowohl benutzerdefinierte als auch implizit definierte Destruktoren ruft der Compiler, nachdem der Rumpf des Destruktors ausgeführt wurde und alle automatischen Objekte, die innerhalb des Rumpfs allokiert wurden, zerstört wurden, die Destruktoren für alle nicht-statischen nicht-varianten Datenelemente der Klasse auf, in umgekehrter Reihenfolge ihrer Deklaration, dann ruft er die Destruktoren aller direkten nicht-virtuellen Basisklassen in umgekehrter Reihenfolge der Konstruktion auf (welche wiederum die Destruktoren ihrer Member und ihrer Basisklassen aufrufen, usw.), und dann, wenn dieses Objekt von der am meisten abgeleiteten Klasse ist, ruft es die Destruktoren aller virtuellen Basen auf.
Selbst wenn der Destruktor direkt aufgerufen wird (z.B. obj.~Foo ( ) ; ), gibt die return -Anweisung in ~Foo ( ) die Kontrolle nicht sofort an den Aufrufer zurück: Sie ruft zuerst alle diese Member- und Basis-Destruktoren auf.
Virtuelle Destruktoren
Das Löschen eines Objekts über einen Zeiger auf die Basisklasse ruft undefiniertes Verhalten hervor, sofern der Destruktor in der Basisklasse nicht virtual ist:
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // sicher
Eine gängige Richtlinie besagt, dass ein Destruktor für eine Basisklasse entweder public und virtual oder protected und nonvirtual sein muss.
Reine virtuelle Destruktoren
Ein potenzieller (seit C++20) Destruktor kann als rein virtuell deklariert werden, beispielsweise in einer Basisklasse, die abstrakt gemacht werden muss, aber keine anderen geeigneten Funktionen hat, die als 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-Fehler Derived obj; // OK
Ausnahmen
Wie jede andere Funktion kann auch ein Destruktor durch das Werfen einer Exception beenden (was in der Regel eine explizite Deklaration als noexcept ( false ) erfordert) (seit C++11) , jedoch wird, falls dieser Destruktor während des Stack-Unwindings aufgerufen wird, std::terminate stattdessen aufgerufen.
Obwohl std::uncaught_exceptions manchmal verwendet werden kann, um laufende Stack-Entladung zu erkennen, wird es allgemein als schlechte Praxis angesehen, zuzulassen, dass irgendein Destruktor durch Werfen einer Exception endet. Diese Funktionalität wird dennoch von einigen Bibliotheken verwendet, wie SOCI und Galera 3 , die sich auf die Fähigkeit der Destruktoren von namenlosen Temporaries verlassen, Exceptions am Ende des vollständigen Ausdrucks zu werfen, der das Temporary konstruiert.
std::experimental::scope_success in der Library fundamental TS v3 kann einen potentiell werfenden Destruktor haben , der eine Exception wirft, wenn der Scope normal verlassen wird und die Exit-Funktion eine Exception wirft.
Hinweise
Das direkte Aufrufen eines Destruktors für ein gewöhnliches Objekt, wie eine lokale Variable, ruft undefiniertes Verhalten hervor, wenn der Destruktor erneut am Ende des Gültigkeitsbereichs aufgerufen wird.
In generischen Kontexten kann die Destruktorsyntax auch mit einem Objekt eines Nicht-Klassentyps verwendet werden; dies wird als Pseudo-Destruktoraufruf bezeichnet: siehe member access operator .
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_trivial_union
|
202502L
|
(C++26) | Lockerung der Trivialitätsanforderungen für spezielle Memberfunktionen von Unions |
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
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| CWG 193 | C++98 |
ob automatische Objekte in einem Destruktor
vor oder nach der Zerstörung der Basis- und Mitgliedsubobjekte der Klasse zerstört werden, war nicht spezifiziert |
sie werden zerstört
vor der Zerstörung dieser Subobjekte |
| CWG 344 | C++98 |
die Deklaratorsyntax von Destruktoren war fehlerhaft (hatte das
gleiche Problem wie CWG Issue 194 und CWG Issue 263 |
änderte die Syntax zu einer spezialisierten
Funktionsdeklarator-Syntax |
| CWG 1241 | C++98 |
statische Mitglieder könnten
direkt nach Destruktorausführung zerstört werden |
zerstöre nur nicht-
statische Mitglieder |
| CWG 1353 | C++98 |
die Bedingungen, unter denen implizit deklarierte Destruktoren
undefiniert sind, berücksichtigten keine mehrdimensionalen Array-Typen |
berücksichtige diese Typen |
| CWG 1435 | C++98 |
die Bedeutung von "Klassenname" in der
Deklaratorsyntax von Destruktoren war unklar |
änderte die Syntax zu einer spezialisierten
Funktionsdeklarator-Syntax |
| CWG 2180 | C++98 |
der Destruktor einer Klasse, die keine am meisten abgeleitete Klasse ist,
würde die Destruktoren ihrer virtuellen direkten Basisklassen aufrufen |
er wird diese Destruktoren nicht aufrufen |
| CWG 2807 | C++20 | die Deklarationsspezifizierer könnten consteval enthalten | verboten |