2010-07-05 14 views
40

Ich weiß, dass meine Destruktoren beim normalen Abwickeln des Stapels aufgerufen werden und wenn Ausnahmen ausgelöst werden, aber nicht wenn exit() aufgerufen wird.Unter welchen Umständen werden C++ - Destruktoren nicht aufgerufen?

Gibt es noch andere Fälle, in denen meine Destruktoren nicht aufgerufen werden? Was ist mit Signalen wie SIGINT oder SIGSEGV? Ich nehme an, dass sie für SIGSEGV nicht aufgerufen werden, aber für SIGNINT sind sie, woher weiß ich, welche Signale den Stapel abwickeln werden?

Gibt es andere Umstände, unter denen sie nicht aufgerufen werden?

+12

Wie hier darauf hingewiesen, http://thedailywtf.com/Articles/My-Tales.aspx, können Sie auch die destructor bewusst sein sollte, wird nicht aufgerufen werden, wenn der Netzstecker ist gezogen;). –

+4

SIGINT löscht den Stapel nur, wenn Sie einen Signalhandler installieren, der das Standardverhalten außer Kraft setzt. Standardmäßig führt SIGINT zum sofortigen Programmabbruch. – karunski

+0

Nicht als Antwort, weil es eher wie ein Versehen in der Frage scheint. Destruktoren werden nur am Ende der Lebensdauer von Objekten mit statischer, automatischer oder Thread-Speicherdauer (normalerweise) automatisch aufgerufen. Bei Objekten mit ** dynamischer ** Speicherdauer wird der Destruktor nur aufgerufen, wenn 'delete' für einen Zeiger auf das Objekt aufgerufen wird. So wird ein Destruktor nicht für dynamische Objekte aufgerufen, für die 'delete' nie aufgerufen wird (sei es, weil ein Speicherleck dies unmöglich macht, oder durch Überwachung). –

Antwort

45

Gibt es noch andere Umstände, unter denen [Destruktoren] nicht aufgerufen werden?

  1. weite Sprünge: diese mit der natürlichen Stapelprozess interferieren Abwickeln und oft undefinierten Verhalten in C++ führen.
  2. Vorzeitige Ausstiege (Sie haben bereits darauf hingewiesen, obwohl es erwähnenswert ist, dass das Werfen während des Stapelns aufgrund einer ausgelösten Ausnahme zu undefiniertem Verhalten führt und deshalb sollten wir nie aus Dktoren werfen)
  3. Werfen von einem Konstruktor ruft das dtor für eine Klasse nicht auf. Wenn Sie also mehrere Speicherblöcke zuweisen, die von mehreren verschiedenen Zeigern (und nicht von intelligenten Zeigern) in einem Ctor verwaltet werden, müssen Sie try-Blöcke auf Funktionsebene verwenden oder die Initialisierungsliste vermeiden und einen try/catch-Block im Ctor verwenden body (oder besser noch, benutze einfach einen Smart-Pointer wie scoped_ptr, da jedes bisher in einer Initialisierungsliste erfolgreich initialisierte Element zerstört wird, obwohl die Klasse dtor nicht aufgerufen wird).
  4. Wie bereits erwähnt, konnte ein dtor nicht virtuell erstellt werden, wenn eine Klasse über einen Basiszeiger gelöscht wird. Dies könnte dazu führen, dass die Unterklasse dtors nicht aufgerufen wird (undefiniertes Verhalten).
  5. Fehler beim Aufrufen des übereinstimmenden Operators delete/delete [] für einen Operator new/new [] call (undefiniertes Verhalten - kann dtor nicht aufrufen).
  6. Fehler beim manuellen Aufrufen des dtor, wenn die Verwendung von placement new mit einem benutzerdefinierten Speicherzuordner im Bereich "Deallocate" verwendet wird.
  7. Verwenden von Funktionen wie memcpy, die nur einen Speicherblock in einen anderen kopieren, ohne Kopier-ctors aufzurufen. mem * -Funktionen sind in C++ tödlich, wenn sie die privaten Daten einer Klasse überschreiben, Vtabellen überschreiben usw. Das Ergebnis ist typischerweise undefiniertes Verhalten.
  8. Instanziierung einiger Smart Pointer (auto_ptr) auf einen unvollständigen Typen finden diese discussion
+4

Schöne Liste, aber es gibt einen Fehler in Punkt 3: Sie können tatsächlich einen try-Block um die Initialisiererliste setzen, nachschlagen Funktionsebene versuchen Blöcke: 'struct X {X() versuchen: x_ (42) {} catch (. ..) {} privat: int x_; }; 'Eigentlich kann man das für jede Funktion wie' void foo() try {} catch (...) {} 'verwenden, aber einige wichtige Compiler (VS2008, weiß nicht, ob das in späteren Versionen behoben ist) ersticken es. Der Hinweis über Smart Pointer bleibt jedoch gültig. –

+0

@Fabio Danke Fabio, ich werde darauf hinweisen! Ich war mir dieser Funktionsebenen-Versuch/Fang-Blöcke nicht bewusst (verwende RAII lieber überall, da es Kopfschmerzen lindert). – stinky472

+0

Im Allgemeinen eine schöne Liste. Allerdings sind # 4 und # 5 technisch nicht definiertes Verhalten, so dass der Standard nichts darüber aussagt, ob Destruktoren aufgerufen werden oder nicht. Die meisten Compiler verhalten sich so, wie Sie es sagen (oder können einfach abstürzen). – KeithB

2

abort beendet Programm, ohne Destruktoren für Objekte mit automatischer oder statischer Speicherdauer auszuführen, wie Standard sagt. Für andere Situationen sollten Sie implementierungsspezifische Dokumente lesen.

3

Ein Signal selbst nicht die Ausführung von beeinflusst der aktuelle Thread und damit der Aufruf von Destruktoren, weil es ein anderen Ausführungskontext mit einem eigenen Stapel ist, wo Ihre Objekte sind nicht vorhanden. Es ist wie ein Interrupt: Es wird irgendwo außerhalb Ihres Ausführungskontexts behandelt, und wenn es behandelt wird, wird das Steuerelement an Ihr Programm zurückgegeben.

Wie bei Multithreading, C++ die Sprache kennt keine Vorstellung von Signalen. Diese zwei sind vollständig orthogonal zueinander und sind durch zwei nicht verwandte Standards spezifiziert. Wie sie interagieren, hängt von der Implementierung ab, solange sie keinen der Standards bricht.

Als eine Randnotiz ist ein anderer Fall, wenn der Destruktor des Objekts nicht aufgerufen wird, wenn sein Konstruktor eine Ausnahme auslöst. Destruktoren der Mitglieder werden jedoch immer noch aufgerufen.

7

Der C++ Standard sagt nichts darüber, wie bestimmte Signale müssen behandelt werden - viele Implementierungen nicht SIGINT unterstützen kann, usw. Destruktoren nicht aufgerufen werden, wenn exit() oder abort() oder terminate() genannt werden.

Bearbeiten: Ich hatte gerade eine schnelle Suche durch den C++ Standard und ich kann nichts finden, das angibt, wie Signale mit Objektlebenszeiten interagieren - vielleicht jemand mit besseren Standards - fu als ich könnte etwas finden?

Weitere edit: Während eine andere Frage zu beantworten, fand ich dies im Standard:

beim Ausgang aus einem Rahmen (jedoch erreicht), Destruktoren (12.4) sind für alle konstruierten Objekte genannt mit automatischer Speicherdauer (3.7.2) (benannte Objekte oder Provisorien) , die in diesem Bereich deklariert sind, in die umgekehrte Reihenfolge ihrer Deklaration.

So scheint es, dass Destruktoren bei Empfang eines Signals aufgerufen werden müssen.

+0

Beim Empfang eines Signals verlässt die Programmsteuerung das Oszilloskop nicht. Daher gilt der zitierte Standard nicht. Das Standardverhalten von POSIX-Signalhandlern führt nicht zu einem Abwickeln oder Zerstören des Stapels. – karunski

+1

@karunski Es verlässt sicherlich den Bereich, wenn ein Signalhandler installiert ist. –

+0

@Neil Butterworth Sicher, aber das passiert standardmäßig nicht. Die korrektere Schlussfolgerung wäre: "Destruktoren müssen aufgerufen werden, nachdem ein Signal bearbeitet (nicht empfangen) wurde und die Steuerung zu dem Punkt zurückkehrt, an dem der Signalhandler aufgerufen wurde" – karunski

1

Es gibt grundsätzlich zwei Situationen, in denen Destruktoren aufgerufen werden: Bei Stapelabbau am Ende der Funktion (oder bei Ausnahmen), wenn jemand (oder ein Referenzzähler) deletet.

Eine spezielle Situation ist in statischen Objekten zu finden - sie werden am Ende des Programms durch at_exit zerstört, aber dies ist immer noch die 2. Situation.

Welches Signal at_exit durchläuft, hängt davon ab, kill -9 wird den Prozess sofort beenden, andere Signale sagen ihm, dass es beendet werden soll, aber wie genau ist vom Signal Callback abhängig.

3

Ein anderer Fall, in dem sie nicht aufgerufen werden, ist, wenn Sie Polymorphie verwenden und Ihre Basisdestruktoren nicht virtuell gemacht haben.

+1

In diesem Fall erhalten Sie undefiniertes Verhalten. –

2

Wenn eine Funktion oder Methode hat eine Spezifikation wirft und wirft etwas durch die Spezifikation abgedeckt nicht, wird das Standardverhalten sofort verlassen. Der Stapel wird nicht abgewickelt und Destruktoren werden nicht aufgerufen.

POSIX-Signale sind ein Betriebssystem-spezifisches Konstrukt und haben keine Vorstellung von C++ - Objektbereich. Im Allgemeinen können Sie nichts mit einem Signal tun, außer vielleicht, fangen Sie es ab, setzen Sie eine globale Flag-Variable und behandeln Sie es später in Ihrem C++ - Code, nachdem der Signal-Handler beendet wurde.

In den letzten Versionen von GCC können Sie eine Ausnahme innerhalb der synchronen Signalbehandlungsroutinen auslösen, was zu dem erwarteten Abwicklungs- und Zerstörungsprozess führt. Dies ist sehr Betriebssystem und Compiler-spezifische, obwohl

2

Viele Antworten hier, aber immer noch unvollständig!

Ich fand einen anderen Fall, in dem Destruktoren nicht ausgeführt werden. Dies geschieht immer dann, wenn die Ausnahme über eine Bibliothekgrenze hinweg abgefangen wird.

Weitere Details hier:

Destructors not executed (no stack unwinding) when exception is thrown

+0

Das scheint ein Fehler zu sein, da ich bezweifle, dass die C++ - Spezifikation ein solches Verhalten zulässt. – WilliamKF

+0

Die Frage ist hier: "Unter welchen Umständen Destruktoren nicht aufgerufen werden". Auch wenn das ein Fehler in Visual Studio ist, ist es eine gültige Antwort auf die Frage, da ein Fehler auch ein Problem ist. Menschen kommen von Google hierher und einige von ihnen können nur dieses spezifische Problem erfahren, ohne zu wissen, ob es ein Bug ist oder nicht. – Elmue

Verwandte Themen