Destruktor und this Zeiger
-
Simon2 schrieb:
hustbaer schrieb:
this->~T() ist AFAIK auch wohldefiniert, wenn man es richtig anwendet (was aber nicht unbedingt einfach ist).
hmmmm - sowas kann ja eigentlich nur innerhalb einer Memberfunktion passieren ... kann man da dann ein placement-new (auf this) machen?
Gibt's überhaupt einen Fall, in dem man explizit den Dtor aufruft und danach NICHT placement-new ?Nö, eigentlich nicht. Es ist eben hauptsächlich in Containerklassen (lies: deren Allokatoren) sinnvoll.
... und wie kann man innerhalb einer Memberfunktion das Objekt löschen ("delete this") ? Ich dachte immer, dabei würde u.a. auch die vtable abgeräumt - wie soll's mit der aufrufenden Funktion "weitergehen" ?
Klar, die vtable wird aufgeräumt, aber das ist ja egal. Die aktuelle Funktion wird ganz normal weiter ausgeführt und solange Du danach nicht auf Datenmembers zugreifst oder virtuelle Funktionen aufrufst, ist alles in Butter (Parashift behauptet, man dürfe überhaupt keine Funktionen mehr aufrufen, das glaube ich aber nicht ohne weiteres).
-
Konrad Rudolph schrieb:
...Klar, die vtable wird aufgeräumt, aber das ist ja egal. Die aktuelle Funktion wird ganz normal weiter ausgeführt und solange Du danach nicht auf Datenmembers zugreifst oder virtuelle Funktionen aufrufst, ist alles in Butter...
hmmm um den Dtor muss man sich natürlich gesondert kümmern (darf gar nicht mehr aufrufbar sein oder { non-virtual und "stateless wiederholbar" }....
Gruß,
Simon2.
-
SORRY.
Ich meinte eigentlich nicht this->~T() sondern nur p->~T().this->~T() ist zwar auch wohldefiniert, nur ist wirklich die Frage wo man es sinnvoll einsetzen könnte.
Eine Sache die mir einfällt wäre wenn man den Platz für ein Singleton statisch reservieren will, das Aufräumen der Instanz allerdings über Intrusive-Reference-Counting steuern. Zugegebenermassen sehr weit hergeholt, aber in dem Fall könnte es einem passieren dass man irgendwo this->~T() schreibt, und es Sinn macht (und auch "OK" ist).
---
kann man da dann ein placement-new (auf this) machen?
Nope, da man auf "this" danach nichtmehr zugreifen sollte, zumindest so wie ich den Standard verstehe. Man könnte sich den "this" Zeiger allerdings vorher in einen void* kopieren, und diesen void* dann danach für placement-new verwenden. Kann aber auch sein ich bin in der Auslegung des Standards da etwas zu paranoid, und man darf es auch ohne diesen Umweg machen. ("Können" tut man es sicher, kenne keinen Compiler der da Probleme machen würde)
Gibt's überhaupt einen Fall, in dem man explizit den Dtor aufruft und danach NICHT placement-new ?
Klar, z.B. in Containern, hat ja der Konrad schon erwähnt. Nämlich z.B. wenn ein std::vector sich gerade "resized", und während des Umkopierens eine Exception fliegt. Dann muss man die bereits konstruierten Objekte wieder "zurückbasteln" (also zerstören). Danach hat man wieder "rohen Speicher", den gibt man dann frei, und alles ist wieder gut

-
Lustig, mir ist heute auf Arbeit aufgefallen, dass ich das in meinem Code verwende. Hatte ich ganz vergessen.
Und zwar brauchen meine Objekte quasi eine `reset`-Methode und das ganze soll außerdem recht effizient laufen bitteschön. Die Methode ist folgendermaßen implementiert:
struct T { … void reset() { this->~T(); new (this) T(); } };Das könnte man vielleicht sogar zum Idiom erheben (CRTP, anyone?) – zum Glück braucht man solche Reset-Methoden aber nicht sehr häufig, daher kann man's sich eigentlich auch schenken.
-
Zum Glück kann man, wenn man Einfluss auf die Klassen hat die solche Funktionen brauchen, das auch anders implementieren

-
hustbaer schrieb:
Zum Glück kann man, wenn man Einfluss auf die Klassen hat die solche Funktionen brauchen, das auch anders implementieren

Und zwar wie? Beachte, dass ich kein Fan von diese `init`-Methoden bin – d.h. ich will im Konstruktor keine andere Methode aufrufen sondern nur Members initialisieren. Und Codeduplikation will ich natürlich auch vermeiden.
Um das ganze interessanter zu machen, können wir auch annehmen, dass einige Members `const` sind. Wobei das dann natürlich schon hart an die Grenze des guten Geschmacks geht.
-
Das könnte man doch mit einem Standardkonstruktor und selbst definierten swap lösen oder?
-
Braunstein schrieb:
Das könnte man doch mit einem Standardkonstruktor und selbst definierten swap lösen oder?
Stimmt. Aber: selbstdefiniertes Swap = Codeduplikation. Aber zugegeben, das ist nicht mehr stichhaltig, denn dieselbe Codeduplikation hat man, sobald man sich einen Kopierkonstruktor schreibt.
-
@ Konrad Rudolph:
Wegen der "Effizienz": Wenn du eineReset()-Methode schreibst, führst du wahrscheinlich einige Zuweisungen bzw. Allokationen und Löschungen in der Klasse durch. Meinst du nicht, das geht schneller als das komplette Zerstören und Neu-Konstruieren des Objekts?
-
Nexus schrieb:
@ Konrad Rudolph:
Wegen der "Effizienz": Wenn du eineReset()-Methode schreibst, führst du wahrscheinlich einige Zuweisungen bzw. Allokationen und Löschungen in der Klasse durch. Meinst du nicht, das geht schneller als das komplette Zerstören und Neu-Konstruieren des Objekts?Wo ist der Unterschied? Selbst bei Non-PODs sollte es keinen geben, meine Klasse enthält aber sowieso nur PODs. Also, effizienter bekommt man meinen Code glaube ich wirklich nicht mehr.
-
An dieser Stelle noch eine Frage.
Folgendes Code-Stück sei gegeben:
Methode von Klasse1:
void Klasse1::funktion1() { .. zeiger_auf_Klasse2_objekt->funktion2(this); }Methode von Klasse2:
void Klasse2::funktion2(Klasse1* k1) { .. delete k1; }Das sollte doch ohne Probleme funktionieren oder?
(vorrausgesetzt Klasse1 wurde mit new erzeugt und es wird nicht mehr darauf zugegriffen nachdem funktion1 aufgerufen wurde)
-
Konrad Rudolph schrieb:
Wo ist der Unterschied? Selbst bei Non-PODs sollte es keinen geben, meine Klasse enthält aber sowieso nur PODs. Also, effizienter bekommt man meinen Code glaube ich wirklich nicht mehr.
Ich bin mir da nicht sicher. Wird bei Klassen im Konstruktor nie mehr gemacht als nur Member initialisiert? Oder ist das nur bei polymorphen der Fall?
xorm schrieb:
Das sollte doch ohne Probleme funktionieren oder?
(vorrausgesetzt Klasse1 wurde mit new erzeugt und es wird nicht mehr darauf zugegriffen nachdem funktion1 aufgerufen wurde)Nein - das Codestück ist kritisch, weil die Funktion der Klasse selbst noch am Laufen ist, während das Objekt schon zerstört wurde. Zudem wird nachher normalerweise nochmals der Destruktor der (jetzt toten) Klasse aufgerufen.
Du kannst dir also merken: Objekte können sich nicht selber zerstören. Als "selbst" gilt auch, wenn eine Methode eine andere Funktion aufruft, die das Objekt destruiert. Sowie auch Funktionen, die sich gegenseitig aufrufen, rekursiv sind.
-
Nexus schrieb:
xorm schrieb:
Das sollte doch ohne Probleme funktionieren oder?
(vorrausgesetzt Klasse1 wurde mit new erzeugt und es wird nicht mehr darauf zugegriffen nachdem funktion1 aufgerufen wurde)Nein - das Codestück ist kritisch, weil die Funktion der Klasse selbst noch am Laufen ist, während das Objekt schon zerstört wurde. Zudem wird nachher normalerweise nochmals der Destruktor der (jetzt toten) Klasse aufgerufen.
Daß die Funktion selbst noch am Laufen ist, sollte ja kein Problem sein. Member-Funktionen von Klassen sind schließlich auch nur "normale" Funktionen, denen implizit der this-Zeiger übergeben wird. Nur auf das Objekt selbst darf nach dem delete natürlich in keinster Weise mehr zugegriffen werden.
Und warum sollte der Destruktor nochmal aufgerufen werden? Daß man nicht ein zweites Mal den selben Zeiger löschen darf ist schon klar, aber das gilt ja unabhängig davon, wer das delete aufruft.
Mit einem privaten Destruktor könnte man außerdem sicherstellen, daß niemand anders das Objekt zerstören kann, und daß sich keine Objekte der Klasse auf dem Stack erzeugen lassen.
-
Nexus schrieb:
Du kannst dir also merken: Objekte können sich nicht selber zerstören.
Doch, können sie. Das war ja gerade der Punkt.
-
dooooomi schrieb:
Daß die Funktion selbst noch am Laufen ist, sollte ja kein Problem sein.
Aber kann man sich auch drauf verlassen? Es gibt ja genügend Dinge, die in 99% der Fälle funktionieren...
dooooomi schrieb:
Und warum sollte der Destruktor nochmal aufgerufen werden?
Ich ging dabei von Stack-Deklarationen aus.
Konrad Rudolph schrieb:
Doch, können sie. Das war ja gerade der Punkt.
Hm, dann hab ich das wohl nicht mitgekriegt. Aber mal abgesehen davon, wie sicher das ist - ich finde es vom Design her eher fragwürdig, wenn sich ein Objekt selbst zerstören sollte, gerade mit solchen Dingen wie Destruktor privat und nur
newfür die Konstruktion zulassen...
-
Nexus schrieb:
dooooomi schrieb:
Daß die Funktion selbst noch am Laufen ist, sollte ja kein Problem sein.
Aber kann man sich auch drauf verlassen? Es gibt ja genügend Dinge, die in 99% der Fälle funktionieren...
Also, zumindest laut FAQ ist "Selbstmord" von Objekten erlaubt. Ich gehe einfach mal davon aus, daß im C++-Standard nichts gegenteiliges steht.
Nexus schrieb:
Aber mal abgesehen davon, wie sicher das ist - ich finde es vom Design her eher fragwürdig, wenn sich ein Objekt selbst zerstören sollte, gerade mit solchen Dingen wie Destruktor privat und nur
newfür die Konstruktion zulassen...In den allermeisten Fällen gibt es sicherlich bessere Lösungen. Aber ich würde
delete this(oder eine indirekte Variante davon) nicht zwangsläufig als schlechtes Design ansehen. Manchmal weiß ein Objekt eben selbst am besten, wenn es nicht mehr gebraucht wird.
-
Schreibt anständige Programme, dann braucht ihr sowas komisches garnicht erst.
-
Der "Paradefall" hier wäre intrusive reference counting, wie es z.B. bei COM verwendet wird. (Und in hunderten Frameworks und ...).
Natürlich könnte man mit dem einen oder anderen Trick das "delete" aus der eigentlichen Klasse rausbekommen. Macht aber IMO keinen Sinn, da ja nix "böse" daran ist.