Virtueller Destruktor
-
Ich hab eine frage. Ich hab genau zwei Klassen:
class A { public: virtual void Call () = 0; }; class B : public A { public: void Call () { cout<<m_Num<<endl; } int m_Num; };
Brauche ich in genau dem Fall einen virtuellen Destruktor?
Ich habe folgendes gefunden http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7.
Da steht man soll bei Klassen mit virtuellen Funktionen immer einen virtuellen Destruktor verwenden.Außerdem steht da noch, dass mich ein virtueller Destruktor in diesem Fall nichts kostet. Wie kann ich mir das vorstellen?
-
Ich würde sagen man brauchst immer dann einen virtuellen Dtor, wenn Destruktoren von abgeleiteten Klassen Speicher freigeben bzw. aufgerufen werden müssen.
-
Destruktor immer virtuell, wenn du von der Klasse public erbst.
Virtuelle Funktionen stehen in einer Tabelle, die jedem Objekt bei der Erstellung zugeiesen wird, damit es weiß, welche Funktionen es aufrufen muss. Hast du bereits virtuelle Funktionen, hat das Objekt auch bereits intern einen Zeiger auf diese Tabelle, es muss also kein weiterer hinzugefügt werden, im Gegensatz zu der ersten virtuellen Funktion, die hinzugefügt wird.
-
Herb Sutter schreibt dazu in Exceptional C++ Style:
"A base class destructor should be either public and virtual, or protected and nonvirtual."Sprich: Wenn Du dem Anwender die Möglichkeit nimmst, Dein Objekt über einen Basisklassenzeiger zu löschen, braucht der Destructor auch nicht virtuell sein. Wenn Du später nur noch mit Basisklassenzeigern hantierst, wäre das natürlich sinnvoll.
-
this->that schrieb:
Ich würde sagen man brauchst immer dann einen virtuellen Dtor, wenn Destruktoren von abgeleiteten Klassen Speicher freigeben bzw. aufgerufen werden müssen.
Das war auch mein Gedanke, aber zählt das, dass da in Klasse B ein Integer gespeichert ist, als Speicher frei geben? Ich schätze mal, sobald statt dem Integer ein Object als Kopie gespeichert ist, würde dessen Desktuktor nicht mehr aufgerufen...
class A { public: virtual void Call () = 0; }; class C { public: C () { p = new int[10]; } ~C () { delete [] p; } int *p; }; class B : public A { public: void Call () { } C c; };
-
ProgChild schrieb:
Das war auch mein Gedanke, aber zählt das, dass da in Klasse B ein Integer gespeichert ist, als Speicher frei geben? Ich schätze mal, sobald statt dem Integer ein Object als Kopie gespeichert ist, würde dessen Desktuktor nicht mehr aufgerufen...
Das hat garnichts zu sagen. Das Löschen eines Basisklassenzeigers dessen Destruktor nicht virtuell ist ist undefiniert. Da gibt es nichts dran zu rütteln
-
LordJaxom schrieb:
Das hat garnichts zu sagen. Das Löschen eines Basisklassenzeigers dessen Destruktor nicht virtuell ist ist undefiniert. Da gibt es nichts dran zu rütteln
Na gut. Aber nochmal wegen den Kosten. Auch wenn es mich keinen Speicher kostet, so kostet es mich doch den Umweg über die VTable, also hab ich eine dereferenzierung mehr. Sehe ich das so richtig?
-
LordJaxom schrieb:
ProgChild schrieb:
Das war auch mein Gedanke, aber zählt das, dass da in Klasse B ein Integer gespeichert ist, als Speicher frei geben? Ich schätze mal, sobald statt dem Integer ein Object als Kopie gespeichert ist, würde dessen Desktuktor nicht mehr aufgerufen...
Das hat garnichts zu sagen. Das Löschen eines Basisklassenzeigers dessen Destruktor nicht virtuell ist ist undefiniert. Da gibt es nichts dran zu rütteln
Du meinst eines Basisklassenzeiger, der auf eine Instanz einer abgeleiteten Klasse zeigt?
Wenn meine abgeleiteten Klassen irgendwas im Dtor machen, dann will ich, dass die aufgerufen werden und mach folglich den Dtor der Basisklasse virtuell. Ansonsten braucht der doch auch nicht virtuell zu sein.
-
this->that schrieb:
Wenn meine abgeleiteten Klassen irgendwas im Dtor machen, dann will ich, dass die aufgerufen werden und mach folglich den Dtor der Basisklasse virtuell. Ansonsten braucht der doch auch nicht virtuell zu sein.
Wenn aber die Abgeleitete Klasse selbst nichts im Destruktor macht, dafür aber Member-Objekte (auf dem Stack, also nicht als Pointer), die einen Destruktor haben der was macht.
Wird dieser Destruktor aufgerufen, wenn der von der Abgeleiteten Klasse nicht aufgerufen wird. Ich schätze nämlich mal, dass die zusammen hängen.
-
this->that schrieb:
Du meinst eines Basisklassenzeiger, der auf eine Instanz einer abgeleiteten Klasse zeigt?
Ja, das habe ich mal impliziert.
Wenn meine abgeleiteten Klassen irgendwas im Dtor machen, dann will ich, dass die aufgerufen werden und mach folglich den Dtor der Basisklasse virtuell. Ansonsten braucht der doch auch nicht virtuell zu sein.
*hust* undefiniert *hust* - auch wenn der abgeleitete Destruktor vom Compiler erzeugt wurde, ist er nicht wirklich leer.
-
LordJaxom schrieb:
*hust* undefiniert *hust* - auch wenn der abgeleitete Destruktor vom Compiler erzeugt wurde, ist er nicht wirklich leer.
Das wurde mir auch so eben bewusst...
Hätte mir auch früher einfallen können. Naja danke auf jeden Fall, an alle!
Hab jetzt nun folgendes getestet:
#include <iostream> #include <string> using namespace std; class A { public: virtual void Call () = 0; }; class C { public: C () { p = new int[10]; cout<<"Ctor called"<<endl; } ~C () { delete [] p; cout<<"Dtor called"<<endl; } int *p; }; class B : public A { public: void Call () { } C c; }; int main () { B* b = new B(); A* a = b; delete a; return 0; }
Das liefert, wie erwartet, folgendes:
$ g++ -Wall -ansi test.cpp && ./a.out test.cpp:5: warning: ‘class A’ has virtual functions but non-virtual destructor test.cpp:24: warning: ‘class B’ has virtual functions but non-virtual destructor Ctor called $
Der zweite Test:
#include <iostream> #include <string> using namespace std; class A { public: virtual void Call () = 0; }; class B : public A { public: void Call () { } int m_Num; }; int main () { B* b = new B(); A* a = b; delete a; return 0; }
Liefert dann das hier:
$ g++ -Wall -ansi test2.cpp && valgrind ./a.out test2.cpp:5: warning: ‘class A’ has virtual functions but non-virtual destructortest2.cpp:10: warning: ‘class B’ has virtual functions but non-virtual destructor ==5694== Memcheck, a memory error detector. ==5694== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al. ==5694== Using LibVEX rev 1658, a library for dynamic binary translation. ==5694== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP. ==5694== Using valgrind-3.2.1-Debian, a dynamic binary instrumentation framework. ==5694== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al. ==5694== For more details, rerun with: -v ==5694== ==5694== ==5694== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 17 from 1) ==5694== malloc/free: in use at exit: 0 bytes in 0 blocks. ==5694== malloc/free: 1 allocs, 1 frees, 8 bytes allocated. ==5694== For counts of detected errors, rerun with: -v ==5694== All heap blocks were freed -- no leaks are possible.
Das scheint also fehlerfrei zu funktionieren. Ich schätze aber mal, dass es nicht Standardkonform ist.
Also nochmal, danke!
Edit: Ist auch alles in Allem recht logisch... *brett-vorm-kopf-gehabt-hab*
-
Das ist eine der Sachen die AFAIK laut Standard "undefined behaviour" sind, aber fast überall so funktionieren wie man es erwartet.
Genauso wie es fast überall funktioniert ein Array von Objekten mit trivialem Dtor mittels "delete-ohne-[]" freizugeben, obwohl der Standard sagt dass es pöse ist.Nur... dass es geht heisst ja nicht dass man es so machen muss.
Vor allem - wenn man schon dynamische Speicherverwaltung verwendet (=langsam), dann kann man sich den einen virtual call auch leisten. Wenn das Objekt auf dem Stack gelebt hat und zerstört wird dann wird der Dtor sowieso nicht virtuell aufgerufen sondern direkt (da die genaue Klasse bekannt ist).
-
LordJaxom schrieb:
Herb Sutter schreibt dazu in Exceptional C++ Style:
"A base class destructor should be either public and virtual, or protected and nonvirtual."Sprich: Wenn Du dem Anwender die Möglichkeit nimmst, Dein Objekt über einen Basisklassenzeiger zu löschen, braucht der Destructor auch nicht virtuell sein. Wenn Du später nur noch mit Basisklassenzeigern hantierst, wäre das natürlich sinnvoll.
Eine grundlegende Klassenvernichtung sollte eines öffentlich und virtuell oder geschützt und nicht virtuell sein.
Mehr sagt das nicht aus. Punkt.
-
Synec schrieb:
LordJaxom schrieb:
Herb Sutter schreibt dazu in Exceptional C++ Style:
"A base class destructor should be either public and virtual, or protected and nonvirtual."Sprich: Wenn Du dem Anwender die Möglichkeit nimmst, Dein Objekt über einen Basisklassenzeiger zu löschen, braucht der Destructor auch nicht virtuell sein. Wenn Du später nur noch mit Basisklassenzeigern hantierst, wäre das natürlich sinnvoll.
Eine grundlegende Klassenvernichtung sollte eines öffentlich und virtuell oder geschützt und nicht virtuell sein.
Mehr sagt das nicht aus. Punkt.
Stimmt, aber wenn man bei Sutter ein wenig mehr liest sagt er noch, dass man die public-virtual Variante nur dann nutzen soll, wenn Polymorphie erlaubt ist. Sprich: bei protected-nonvirtual Dtoren wird sofort ersichtlich, dass Polymorphie nicht erwünscht ist.
Wenn du aber schon virtuelle Member hast deutet das auf gewünschte Polymorphie hin, daher public virtual.
-
Synec schrieb:
Eine grundlegende Klassenvernichtung sollte eines öffentlich und virtuell oder geschützt und nicht virtuell sein.
Mehr sagt das nicht aus. Punkt.
Siehe auch pumuckl, aber ich geh trotzdem nochmal drauf ein:
Danke für den offensichtlichen Trollversuch (habe ich "Unregs nerven" wohl doch zu früh aus meiner Signatur entfernt). Natürlich erklärt Sutter im Folgenden auch warum er diese Richtlinie aufstellt. Ich hatte (aus Faulheit sowie aus Copyright-Gründen) nur darauf verzichtet, das komplette Kapitel abzutippen.