Namensräume
Varianten
Aktionen

Destruktoren

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Kopierzuweisung
Move-Zuweisung (C++11)
Destruktor
Templates
Sonstiges
 
 

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)

Inhalt

[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 -

dynamische Ausnahme-Spezifikationen

(bis C++11)

entweder dynamische Ausnahmespezifikation
oder noexcept-Spezifikation

(seit C++11)
(bis C++17)

noexcept-Spezifikation

(seit C++17)
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

  • 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

  • Thread-Ende für Objekte mit Thread-lokaler 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 Destruktor

Eine 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 T kein benutzerdefinierter potenzieller Destruktor bereitgestellt wird, deklariert der Compiler immer einen implizit deklarierten, und der implizit deklarierte potenzielle Destruktor ist auch der Destruktor für T.

#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*

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 Destruktor

Der implizit deklarierte oder explizit standardmäßig definierte Destruktor für die Klasse T wird als gelöscht definiert, wenn eine der folgenden Bedingungen erfüllt ist

  • gelöscht oder vom Destruktor von T aus nicht zugänglich ist, oder
  • im Falle eines Unterobjekts als Variant-Member gilt, nicht-trivial ist.
(bis C++26)
  • T ist keine Union und hat ein nicht-variantisches potenziell konstruierbares Unterobjekt vom Klassentyp M (oder möglicherweise ein mehrdimensionales Array davon), so dass M einen Destruktor hat, der vom Destruktor von T aus gelöscht oder nicht zugänglich ist.
  • T ist eine Union und eine der folgenden Bedingungen ist erfüllt
  • Die Überladungsauflösung zur Auswahl eines Konstruktors zur standardmäßigen Initialisierung eines Objekts vom Typ T schlägt fehl oder wählt einen Konstruktor, der entweder gelöscht oder nicht-trivial ist.
  • T hat einen Variant-Member V vom Klassentyp M (oder möglicherweise ein mehrdimensionales Array davon), wobei V einen Standardinitialisierer hat und M einen Destruktor hat, der nicht-trivial ist.
(seit C++26)
  • eine Mehrdeutigkeit, oder
  • eine Funktion, die gelöscht oder vom Destruktor aus nicht zugänglich ist.

Ein explizit standardmäßig definierter potenzieller Destruktor für T wird als gelöscht definiert, wenn er nicht der Destruktor für T ist.

(seit C++20)
(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.
  • Jedes nicht-statische Datenmember vom Klassentyp (oder Array vom Klassentyp) hat einen trivialen Destruktor.
(bis C++26)
  • Entweder ist T eine Union, oder jedes nicht-variante nicht-statische Datenmember vom Klassentyp (oder Array vom Klassentyp) hat einen trivialen Destruktor.
(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

[edit] Siehe auch