Im folgenden Text werde ich zwischen Objekten mit Gültigkeitsbereich unterscheiden , deren Zerstörungszeitpunkt statisch durch ihren umschließenden Bereich (Funktionen, Blöcke, Klassen, Ausdrücke) bestimmt wird, und dynamischen Objekten , deren genaue Zerstörungszeit zum Zeitpunkt der Laufzeit im Allgemeinen nicht bekannt ist.
Während die Zerstörungssemantik von Klassenobjekten durch Destruktoren bestimmt wird, ist die Zerstörung eines skalaren Objekts immer ein No-Op. Insbesondere wird eine Zeigervariable zerstörenden nicht zerstören die pointee.
Objekte mit Gültigkeitsbereich
automatische Objekte
Automatische Objekte (üblicherweise als "lokale Variablen" bezeichnet) werden in umgekehrter Reihenfolge ihrer Definition zerstört, wenn der Kontrollfluss den Umfang ihrer Definition verlässt:
void some_function()
{
Foo a;
Foo b;
if (some_condition)
{
Foo y;
Foo z;
} <--- z and y are destructed here
} <--- b and a are destructed here
Wenn während der Ausführung einer Funktion eine Ausnahme ausgelöst wird, werden alle zuvor erstellten automatischen Objekte zerstört, bevor die Ausnahme an den Aufrufer weitergegeben wird. Dieser Vorgang wird als Abwickeln des Stapels bezeichnet . Während des Abwickelns des Stapels dürfen keine weiteren Ausnahmen die Destruktoren der zuvor erwähnten zuvor konstruierten automatischen Objekte verlassen. Andernfalls wird die Funktion std::terminate
aufgerufen.
Dies führt zu einer der wichtigsten Richtlinien in C ++:
Zerstörer sollten niemals werfen.
nicht lokale statische Objekte
Statische Objekte, die im Namespace-Bereich definiert sind (üblicherweise als "globale Variablen" bezeichnet), und statische Datenelemente werden in umgekehrter Reihenfolge ihrer Definition nach der Ausführung von main
:
struct X
{
static Foo x;
};
Foo a;
Foo b;
int main()
{
} <--- y, x, b and a are destructed here
Foo X::x;
Foo y;
Beachten Sie, dass die relative Reihenfolge der Konstruktion (und Zerstörung) statischer Objekte, die in verschiedenen Übersetzungseinheiten definiert sind, nicht definiert ist.
Wenn eine Ausnahme den Destruktor eines statischen Objekts verlässt, wird die Funktion std::terminate
aufgerufen.
lokale statische Objekte
In Funktionen definierte statische Objekte werden erstellt, wenn (und wenn) der Steuerungsfluss zum ersten Mal ihre Definition durchläuft. 1
Sie werden in umgekehrter Reihenfolge nach der Ausführung von main
:
Foo& get_some_Foo()
{
static Foo x;
return x;
}
Bar& get_some_Bar()
{
static Bar y;
return y;
}
int main()
{
get_some_Bar().do_something();
get_some_Foo().do_something();
} <--- x and y are destructed here
Wenn eine Ausnahme den Destruktor eines statischen Objekts verlässt, wird die Funktion std::terminate
aufgerufen.
1: Dies ist ein extrem vereinfachtes Modell. Die Initialisierungsdetails von statischen Objekten sind tatsächlich viel komplizierter.
Unterobjekte der Basisklasse und Unterobjekte der Mitglieder
Wenn der Steuerungsfluss den Destruktorkörper eines Objekts verlässt, werden seine Elementunterobjekte (auch als "Datenelemente" bezeichnet) in umgekehrter Reihenfolge ihrer Definition zerstört. Danach werden die Unterobjekte der Basisklasse in umgekehrter Reihenfolge der Basis-Spezifizierer-Liste zerstört:
class Foo : Bar, Baz
{
Quux x;
Quux y;
public:
~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects
Wenn während der Erstellung eines der Foo
Unterobjekte eine Ausnahme ausgelöst wird , werden alle zuvor erstellten Unterobjekte zerstört, bevor die Ausnahme weitergegeben wird. Der Foo
Destruktor hingegen wird nicht ausgeführt, da das Foo
Objekt nie vollständig erstellt wurde.
Beachten Sie, dass der Destruktorkörper nicht für die Zerstörung der Datenelemente selbst verantwortlich ist. Sie müssen einen Destruktor nur schreiben, wenn ein Datenelement ein Handle für eine Ressource ist, die freigegeben werden muss, wenn das Objekt zerstört wird (z. B. eine Datei, ein Socket, eine Datenbankverbindung, ein Mutex oder ein Heapspeicher).
Array-Elemente
Array-Elemente werden in absteigender Reihenfolge zerstört. Wenn während der Konstruktion des n-ten Elements eine Ausnahme ausgelöst wird , werden die Elemente n-1 bis 0 zerstört, bevor die Ausnahme weitergegeben wird.
temporäre Objekte
Ein temporäres Objekt wird erstellt, wenn ein prvalue-Ausdruck vom Klassentyp ausgewertet wird. Das bekannteste Beispiel für einen prvalue-Ausdruck ist der Aufruf einer Funktion, die ein Objekt nach Wert zurückgibt, z T operator+(const T&, const T&)
. Unter normalen Umständen wird das temporäre Objekt zerstört, wenn der vollständige Ausdruck, der den pr-Wert lexikalisch enthält, vollständig ausgewertet wird:
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
Der obige Funktionsaufruf some_function(a + " " + b)
ist ein vollständiger Ausdruck, da er nicht Teil eines größeren Ausdrucks ist (stattdessen ist er Teil einer Ausdrucksanweisung). Daher werden alle temporären Objekte, die während der Auswertung der Unterausdrücke erstellt werden, im Semikolon zerstört. Es gibt zwei solche temporären Objekte: Das erste wird während der ersten Addition konstruiert und das zweite wird während der zweiten Addition konstruiert. Das zweite temporäre Objekt wird vor dem ersten zerstört.
Wenn während des zweiten Hinzufügens eine Ausnahme ausgelöst wird, wird das erste temporäre Objekt ordnungsgemäß zerstört, bevor die Ausnahme weitergegeben wird.
Wenn eine lokale Referenz mit einem prvalue-Ausdruck initialisiert wird, wird die Lebensdauer des temporären Objekts auf den Bereich der lokalen Referenz erweitert, sodass Sie keine baumelnde Referenz erhalten:
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
} <--- second temporary (a + " " + b) is destructed not until here
Wenn ein prvalue-Ausdruck vom Typ einer Nichtklasse ausgewertet wird, ist das Ergebnis ein Wert und kein temporäres Objekt. Ein temporäres Objekt wird jedoch erstellt, wenn der Wert zum Initialisieren einer Referenz verwendet wird:
const int& r = i + j;
Dynamische Objekte und Arrays
Im folgenden Abschnitt bedeutet "X zerstören " " X zuerst zerstören und dann den zugrunde liegenden Speicher freigeben". In ähnlicher Weise bedeutet " X erstellen " "zuerst genügend Speicher zuweisen und dann dort X erstellen ".
dynamische Objekte
Ein dynamisches Objekt, das über erstellt wurde, wird über p = new Foo
zerstört delete p
. Wenn Sie dies vergessen, delete p
liegt ein Ressourcenleck vor. Sie sollten niemals versuchen, eine der folgenden Aktionen auszuführen, da alle zu undefiniertem Verhalten führen:
- Zerstören Sie ein dynamisches Objekt über
delete[]
(beachten Sie die eckigen Klammern) free
oder auf andere Weise
- Zerstöre ein dynamisches Objekt mehrmals
- Zugriff auf ein dynamisches Objekt, nachdem es zerstört wurde
Wenn während der Erstellung eines dynamischen Objekts eine Ausnahme ausgelöst wird , wird der zugrunde liegende Speicher freigegeben, bevor die Ausnahme weitergegeben wird. (Der Destruktor wird vor der Speicherfreigabe nicht ausgeführt, da das Objekt nie vollständig erstellt wurde.)
dynamische Arrays
Ein über erstelltes dynamisches Array wird über p = new Foo[n]
zerstört delete[] p
(beachten Sie die eckigen Klammern). Wenn Sie dies vergessen, delete[] p
liegt ein Ressourcenleck vor. Sie sollten niemals versuchen, eine der folgenden Aktionen auszuführen, da alle zu undefiniertem Verhalten führen:
- zerstört ein dynamisches Array über
delete
, free
oder jegliche andere Mittel
- Zerstören Sie ein dynamisches Array mehrmals
- Zugriff auf ein dynamisches Array, nachdem es zerstört wurde
Wenn während der Konstruktion des n-ten Elements eine Ausnahme ausgelöst wird , werden die Elemente n-1 bis 0 in absteigender Reihenfolge zerstört, der zugrunde liegende Speicher wird freigegeben und die Ausnahme wird weitergegeben.
(Sie sollen in der Regel lieber std::vector<Foo>
über Foo*
für dynamischen Arrays. Es macht richtig und robusten Code zu schreiben viel einfacher.)
Referenzzählen von intelligenten Zeigern
Ein dynamisches Objekt, das von mehreren std::shared_ptr<Foo>
Objekten verwaltet wird , wird während der Zerstörung des letzten std::shared_ptr<Foo>
Objekts zerstört, das an der Freigabe dieses dynamischen Objekts beteiligt ist.
(Sie sollen in der Regel lieber std::shared_ptr<Foo>
über Foo*
für gemeinsam genutzte Objekte. Es macht viel einfacher , korrekten und robusten Code zu schreiben.)