Reihenfolge von Destruktor-Aufrufen
-
Ein Hallo in die Runde!
Beim Experimentieren mit libsdl (hier: Das Schreiben eines Wrappers für die Struktur SDL_Surface) habe ich aus einem mir unerfindlichen Grund immer wieder Segmentation-Faults erhalten. Um das Problem zumindest logisch nachvollziehen zu können, habe ich das Problem mit einer "Foo"-Klasse abstrahiert und habe festgestellt, dass es mit der Reihenfolge zusammen hängt, wie der Destruktor der Klasse "Foo" aufgerufen wird.
OS: Windows XP-Pro, DevC++
Hier die "Foo"-Klasse nebst Verwendung (vollständige Quelldatei):
#include <iostream> using namespace std; class Foo { private: int value; public: Foo() : value(0) { cout << "Foo: default-ctor" << endl << " object created at " << this << endl << endl; } Foo(int value) : value(value) { cout << "Foo: ctor (int=" << this->value << ")" << endl << " object created at " << this << endl << endl; } virtual ~Foo() { cout << "Foo: dtor (value was " << value << ")" << endl << " object destroyed at " << this << endl << endl; } }; int main(int argc, char** args) { { Foo bar = 1; bar = 2; bar = 3; } return 0; }Das obenstehende Programm produzierte folgende ausgabe auf der Konsole:
Foo: ctor (int=1) object created at 0x22ff50 Foo: ctor (int=2) object created at 0x22ff40 Foo: dtor (value was 2) object destroyed at 0x22ff40 Foo: ctor (int=3) object created at 0x22ff40 Foo: dtor (value was 3) object destroyed at 0x22ff40 Foo: dtor (value was 3) object destroyed at 0x22ff50Die Zeilen 4 bis 14 kann ich nachvollziehen, das ist auch das Verhalten, was ich erwartet habe. Was mich stört ist, dass das Objekt, welches im ersten Konstruktoraufruf (Quelltextzeile 38, Ausgabezeile 1+2) erst zerstört wird, wenn der Block verlassen wird (Quelltextzeile 41) und nicht, wie ich es erwartet hätte, beim zweiten Konstruktoraufruf. Vielmehr wird hier ein zusätzliches Objekt angelegt.
Nun meine Frage: Wie ist dieses Verhalten zu erklären?
Um Erleuchtung bittende Grüße aus dem Sauerland
Heiko
-
Hi, an dem Verhalten ist nichts Merkwürdig.
Foo bar = 1; // C'tor(int) wird aufgerufen // um 2 in ein Foo reinzustecken muss erst nen Temp Foo-Objekt erzeugt werden bar = 2; // bar = Foo(2) C'tor(int), nach Zuweisung temp nicht mehr benötigt -> d'tor bar = 3; // dito } // bar's Scope wurde verlasssen und wurde deswegen zerstört
-
Zur Einführung: http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=copyvsdirect#Answ
Foo bar = 1;Copy-Initialization. Entspricht:
Foo bar = Foo(1);Der Compiler hat die Erzeugung der Kopie allerdings weg-optimiert (darf er) und stattdessen eine direct-initialization gemacht, also:
Foo bar(1);Ergebnis sind die Zeilen 1-3 in der Ausgabe:
Foo: ctor (int=1)
object created at 0x22ff50bar = 2;Da du keinen op=(int) definiert hast, wird folgendes ausgeführt:
bar = Foo(2);Es wird also erst ein temporäres Objekt erzeugt:
Foo: ctor (int=2)
object created at 0x22ff40dieses dann über den (automatisch erzeugten) op=(const Foo&) zugewiesen und danach wieder zerstört:
Foo: dtor (value was 2)
object destroyed at 0x22ff40Gleiches Spiel für die nächste Zeile.
Am Ende des Blocks wird dann schließlich bar zerstört, was zur letzten Meldung führt:
Foo: dtor (value was 3)
object destroyed at 0x22ff50
-
Mit deinem Foo bar = 1 wird der Konstruktor aufgerufen und ein Objekt an 0x22ff50 erstellt. das ist das Objekt was im programm bar heisst.
Bei bar = 2 wird, weil es den operator=(Foo, int) nicht gibt, ein temporaeres Objekt vom Typ Foo erzeugt (implizite Konvertierung von int nach Foo), an 0x22ff40. Dann der durch den Compiler erstellte operator=(Foo, Foo) aufgerufen und das temporaere objekt wieder zerstoert. Dein Objekt bar an 0x22ff50 beinhaltet jetzt den Wert 2.
Das gleiche Spiel nochmal mit implizierter Konvertierung, neuem Objekt (wieder an 0x22ff40, der Platz ist ja wieder frei), operator=(Foo, Foo) und zerstoerung des temporaeren Objekts. Bar beinhaltet jetzt die 3.
Am Ende, bei verlassen des scopes, wird bar ganz normal zerstoert.
-
Vielen Dank für die ausführlichen Kommentare und weiterführenden Informationen. Alles in allem war es wohl eine Anfängerunachtsamkeit meinerseits.
Von temporären Objekten habe ich schon gehört, aber diese nicht in dem Zusammenhang vermutet. Nun bin ich wieder ein Stückchen schlauer geworden.
Fazit der ganzen Aktion:
Explicit is better than implicit - Lasse deinen Compiler weniger Annahmen treffen und du hast weniger Stress.
Erleuchtete Grüße aus dem Sauerland

Heiko