Namespaces
Variants

Destructors

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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)

Inhaltsverzeichnis

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 -

dynamische Ausnahmespezifikation

(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 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:

  • 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

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

Eine 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 T bereitgestellt wird, deklariert der Compiler stets implizit einen , und der implizit deklarierte prospektive Destruktor ist ebenfalls 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)

Potenziell aufgerufener Destruktor

Der Destruktor für die Klasse T wird potenziell aufgerufen in den folgenden Situationen:

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 T wird als gelöscht definiert, wenn eine der folgenden Bedingungen erfüllt ist:

  • gelöscht oder für den Destruktor von T unzugänglich ist, oder
  • im Fall, dass das Unterobjekt ein Varianten-Member ist, nicht-trivial ist.
(bis C++26)
  • T ist keine Union und hat ein nicht- Varianten potenziell konstruiertes Unterobjekt vom Klassentyp M (oder möglicherweise mehrdimensionales Array davon), sodass M einen Destruktor hat, der gelöscht oder für den Destruktor von T unzugänglich ist.
  • T ist eine Union, und eine der folgenden Bedingungen ist erfüllt:
  • Die Überladungsauflösung zur Auswahl eines Konstruktors zur Default-Initialisierung eines Objekts vom Typ T schlägt entweder fehl oder wählt einen Konstruktor aus, der entweder gelöscht oder nicht-trivial ist.
  • T hat ein Varianten-Member V vom Klassentyp M (oder möglicherweise mehrdimensionales Array davon), wobei V einen Default-Initialisierer hat und M einen nicht-trivialen Destruktor hat.
(seit C++26)
  • einer Mehrdeutigkeit, oder
  • einer Funktion, die gelöscht oder für den Destruktor unzugänglich ist.

Ein explizit default-definierter prospektiver Destruktor für T wird als gelöscht definiert, wenn er nicht der Destruktor für T ist.

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

Siehe auch