Im Vererbungskontext kann es Sinn machen Virtual vor den Destructor zu schreiben. Ich frage mich gerade ob es irgendwelche Nachteile hätte allgemein vor jeden Destruktor ein Virtual zu setzen?
Höherer Speicherbedarf, mehr Laufzeit. Wenn auch nur eine einzige virtuelle Methode existiert, legt der Compiler den ganzen Verwaltungsaufwand für virtuelle Methoden an.
fauler Sack schrieb: > Ja, bei virtual musst du ihn in jedem Fall überschreiben. Verwechselt du gerade virtual und abstrakt? Virtual muss man nicht überschreiben.
fauler Sack schrieb: > VMT wird bei einer Vererbung immer angelegt Keineswegs, nur wenn virtual Funktionen existieren. Ein Grundsatz von c++ ist ja: you don't pay for what you don't use.
Maier schrieb: > Ich frage mich gerade ob es irgendwelche Nachteile hätte allgemein vor > jeden Destruktor ein Virtual zu setzen? Zu den genannten Nachteilen kommt noch hinzu, dass man mit einem virtuellen destructor üblicherweise auch dokumentiert, dass die Klasse als Basisklasse gedacht ist. Im modernen C++ braucht man ja auch nur noch relativ selten nicht virtuelle Destruktoren.
>Ein Grundsatz von >c++ ist ja: you don't pay for what you don't use. Dieser Grundsatz liesse sich auch anders realisieren. Wenn Compiler und Linker zusammenarbeiten, können die selbst entscheiden, ob eine VMT erforderlich ist. Im Vergleich zu profile guided optimizations oder LLVM-IR dürfte so etwas trivial sein.
Noch einer schrieb: > Wenn Compiler und Linker zusammenarbeiten, können die selbst > entscheiden, ob eine VMT erforderlich ist. Woher weiß denn der Linker, ob eine (ggf. dynamisch) nachgeladene Bibliothek Funktionen überschreibt? Wenn eine Bibliothek sich ändert und Funktionen hinzufügt, müssten der dynamische Loader den Code der Anwendung ändern und komplett neu linken. Das ist m.W. mit keinem existierenden Tool möglich (ohne richtiges Neu Kompilieren mithilfe des Codes). Und will der Programmierer überhaupt, dass die Funktion immer automatisch in der am meisten abgeleiteten Klasse aufgerufen wird? In Java bspw sieht das anders aus: Da ist dank Bytecode Problem 1 lösbar und Punkt 2 ist halt einfach festgeschrieben. Tatsächlich kann zB die Hotspot VM virtuelle Funktionen in manchen Fällen wegoptimieren, was Java dort effizienter als C und C++ macht.
Noch einer schrieb: >>Ein Grundsatz von >>c++ ist ja: you don't pay for what you don't use. > > Dieser Grundsatz liesse sich auch anders realisieren. Wenn Compiler und > Linker zusammenarbeiten, können die selbst entscheiden, ob eine VMT > erforderlich ist. Das wäre dann eine Optimierung, deren Nutzung das ABI ändert. > Im Vergleich zu profile guided optimizations oder LLVM-IR dürfte so > etwas trivial sein. Ganz so einfach ist es nicht, wenn man z.B. an Plug-Ins denkt, also zur Laufzeit dynamisch nachgeladene Bibliotheken. Mit denen muss das auch funktionieren. Und woher soll der Compiler im Hauptprogramm wissen, ob das Plug-In später mal den virtuellen Destruktor nutzt?
... man ist leicht versucht, über solche Optimierungen von C++ nachzudenken, auch solche die die Binärkompatibilität verbessern würden. Tatsächlich würden solche Änderungen aus C++ praktisch Java machen. Da kann man auch einfach direkt Java nehmen. Ein weiteres Problem: template-Code müsste ggf. neu generiert werden (z.B. std::vector kann sich anders verhalten je nachdem ob der übergebene Typ virtuelle Funktionen hat oder nicht); das dürfte in etwa so schön sein wie export templates, und die wurden als nicht umsetzbar aus dem Standard entfernt.
>zur Laufzeit dynamisch nachgeladene Bibliotheken
Das Problem hast du heute auch schon. Der Compiler muss die
Deklarationen der Klassen in der shared library kennen. Schau dir mal
an, was die Qt für Verrenkungen enthält, damit sie die privat member
nachträglich ändern können.
Funktioniert eigentlich folgende Konstruktion?
-Die Basisklasse aus der shared library hat keine VMT.
-Du leitest eine Klasse ab, die virtuelle Methoden enthält.
-Du übergibst der shared library einen Pointer auf ein Objekt der
abgeleiteten Klasse.
Noch einer schrieb: > Funktioniert eigentlich folgende Konstruktion? > -Die Basisklasse aus der shared library hat keine VMT. > -Du leitest eine Klasse ab, die virtuelle Methoden enthält. > -Du übergibst der shared library einen Pointer auf ein Objekt der > abgeleiteten Klasse. Ja, Objekte der Basisklasse haben keine v'table, Objekte der abgeleiteten Klasse haben eine v'table.
Noch einer schrieb: > Das Problem hast du heute auch schon. Der Compiler muss die > Deklarationen der Klassen in der shared library kennen. Genau... Daher wird angenommen dass die sich nicht ändern. Noch einer schrieb: > Funktioniert eigentlich folgende Konstruktion? Ich glaube schon. Bei(vor!) der Übergabe wird ja automatisch auf die Basis-Klasse gecastet. Natürlich werden dann keine virtuellen Funktionen in der abgeleiteten Klasse aufgerufen.
... und wenn die lib dann ein delete auf den Pointer ausführt, knallt es. Nur deshalb braucht es ja den virtuellen Destruktor. Oliver
Oliver S. schrieb: > ... und wenn die lib dann ein delete auf den Pointer ausführt, knallt > es. Nur wenn der Destruktor der abgeleiteten Klasse überhaupt etwas tut. Das ist aber auch dann der Fall wenn (indirekt) eine Variable mit nichttrivialem Destruktor vorhanden ist, was sich nicht so leicht überblicken lässt... std::is_trivially_destructible kann da helfen.
Noch einer schrieb: >>zur Laufzeit dynamisch nachgeladene Bibliotheken > > Das Problem hast du heute auch schon. Der Compiler muss die > Deklarationen der Klassen in der shared library kennen. Er muss für den Aufruf der virtuellen Memberfunktionen nur die Deklaration der Basisklasse kennen. Das ist ja gerade der Witz dabei. > Schau dir mal an, was die Qt für Verrenkungen enthält, damit sie die > privat member nachträglich ändern können. Ich weiß nicht, was du damit meinst. Wer soll wo private Member nachträglich ändern? > Funktioniert eigentlich folgende Konstruktion? > -Die Basisklasse aus der shared library hat keine VMT. > -Du leitest eine Klasse ab, die virtuelle Methoden enthält. > -Du übergibst der shared library einen Pointer auf ein Objekt der > abgeleiteten Klasse. Wüßte nicht, warum das nicht genau gleich funktionieren sollte, als wären alle Klassen in einem gemeinsamen Executable.
>Wer soll wo private Member nachträglich ändern?
Trolltech hat den Anspruch, ein altes Programm, das mit QT 5.0.0
compiliert wurde, soll mit allen nachfolgenden Qt5 shared libraries
funktionieren.
War nur ein Beispiel, c++ shared libraries sind nicht wirklich optimal
gelöst.
Noch einer schrieb: >>Wer soll wo private Member nachträglich ändern? > > Trolltech hat den Anspruch, ein altes Programm, das mit QT 5.0.0 > compiliert wurde, soll mit allen nachfolgenden Qt5 shared libraries > funktionieren. Ach jetzt weiß ich, was du meinst. Das hat aber erstmal mit virtuellen Memberfunktionen nichts zu tun. Ja, man muss einige Dinge beachten, wenn man eine Bibliothek binärkompatibel zur Vorversion halten und trotzdem flexibel ändern können will. > War nur ein Beispiel, c++ shared libraries sind nicht wirklich optimal > gelöst. Wie so oft gilt: Das Problem kann durch ein zusätzliches Indirektionslevel umgangen werden, wie es bei Qt gemacht wird. Kostet halt Performance. In Java ist das eben schon in die Sprache eingebaut.
Um mal zur ursprünglichen Frage zurückzukommen: Ein virtueller Destruktor macht Sinn, wenn man eine Basisklasse hat und Pointer von deren Typ auf abgeleitete Klassen benutzen will (also Polymorphie). Zeigt ein Basisklassenpointer auf ein Objekt einer abgeleiteten Klasse und man löscht den Basisklassenpointer mit delete, wird der Destruktor der abgeleiteten Klasse nur aufgerufen wenn er virtuell ist. Das heißt: Virtueller Destruktor ist sinnvoll, sobald es mind. eine virtuelle Funktion gibt. Mit dieser zeigt man, dass Polymorphie hier angedacht ist. Außerdem "bezahlt" man nicht mehr, da es dann bereits einen vTable gibt. Den Destruktor immer virtuell zu machen führt nicht zu Fehlern, kostet ein paar Byte pro Objekt und man sagt damit etwas fragwürdiges aus wie: "Benutze meine Klasse nicht polymorph. Falls du es doch tust, habe ich dir schonmal eine potentielle Fehlerquelle ausgeschaltet."
Daniel R. schrieb: > Das heißt: Virtueller Destruktor ist sinnvoll, sobald es mind. eine > virtuelle Funktion gibt. Mit dieser zeigt man, dass Polymorphie hier > angedacht ist. Außerdem "bezahlt" man nicht mehr, da es dann bereits > einen vTable gibt. das hätte man doch aber gleich so in der Sprache verankern sollen. Sobald eine Funktion virtuelle ist, dann ist die Destruktor automatisch virtuell.
Peter II schrieb: > das hätte man doch aber gleich so in der Sprache verankern sollen. Hätte man können, und wäre wahrscheinlich auch sinnvoll gewesen. Aber so ist das eben: Der eine beschwert sich, dass zu wenig automatisch passiert, der andere beschwert sich, dass es schon zu viel ist. Im Prinzip braucht man den virtuellen Destruktor nicht zwingend, wenn man virtuelle Memberfunktionen hat, sondern nur, wenn man eine Instanz der abgeleiteten Klasse über einen Zeiger auf die Basisklasse zerstören will. Auf der anderen Seite wäre der Overhead vernachlässigbar (ein Zeiger mehr in der vtable, aber nur pro Klasse, nicht pro Instanz).
Rolf M. schrieb: > Auf der anderen Seite wäre der Overhead vernachlässigbar (ein > Zeiger mehr in der vtable, aber nur pro Klasse, nicht pro Instanz). Und der Overhead beim Aufruf des Destruktors, inkl. aller nicht möglichen Optimierungen.
mh schrieb: > Rolf M. schrieb: >> Auf der anderen Seite wäre der Overhead vernachlässigbar (ein >> Zeiger mehr in der vtable, aber nur pro Klasse, nicht pro Instanz). > > Und der Overhead beim Aufruf des Destruktors, inkl. aller nicht > möglichen Optimierungen. ... der aber nur nötig ist, wenn auch ein virtueller Destruktor nötig ist. Wenn man das Objekt nicht über einen Basisklassen-Zeiger zerstört, warum sollte der Compiler dann den Umweg über die vtable gehen? Nur weil sie halt da ist?
Peter II schrieb: > Daniel R. schrieb: >> Das heißt: Virtueller Destruktor ist sinnvoll, sobald es mind. eine >> virtuelle Funktion gibt. Mit dieser zeigt man, dass Polymorphie hier >> angedacht ist. Außerdem "bezahlt" man nicht mehr, da es dann bereits >> einen vTable gibt. > > das hätte man doch aber gleich so in der Sprache verankern sollen. > Sobald eine Funktion virtuelle ist, dann ist die Destruktor automatisch > virtuell. Du brauchst den virtuellen d'tor nur, wenn Du ein Objekt über einen Zeiger auf die Basisklasse löschen (deleten) möchtest. Es gibt aber auch Anwendungsfälle, wo dies nicht nötig ist. Dann kann man z.B. immer noch das Löschen der Basisklasse unterbinden (protected d'tor oder private operator delete). Ja, C++ ist halte recht gewachsen, und an vielen Stellen würde man heute andere defaults wählen.
Rolf M. schrieb: > ... der aber nur nötig ist, wenn auch ein virtueller Destruktor nötig > ist. Wenn man das Objekt nicht über einen Basisklassen-Zeiger zerstört, > warum sollte der Compiler dann den Umweg über die vtable gehen? Nur weil > sie halt da ist? Der Compiler muss über die vtable gehen, wenn er nicht 100% sicher sein kann, dass statischer == dynamischer Typ ist. Und das zu erkennen ist für den Compiler nicht immer einfach. Wenn der Destruktor nicht virtuell ist, muss statischer == dynamischer Typ sein, alles andere wäre undefined behaviour. Vermutlich hätte man bei vorhandenen virtuellen Methoden den Destruktor automatisch virtuell machen können, ohne zusätzlichen Overhead, wenn es gleichzeitig den finally specifier gegeben hätte. Aber den gibt es erst seit c++11. Und dann müsste man immer an das finally denken, statt an das virtual und hat nichts gewonnen.
mh schrieb: > Der Compiler muss über die vtable gehen, wenn er nicht 100% sicher sein > kann, dass statischer == dynamischer Typ ist. Und das zu erkennen ist > für den Compiler nicht immer einfach. Hmm, stimmt. Die Klasse könnte ja selbst wieder eine Basisklasse sein.
Daniel R. schrieb: > Um mal zur ursprünglichen Frage zurückzukommen: > Ein virtueller Destruktor macht Sinn, wenn man eine Basisklasse hat und > Pointer von deren Typ auf abgeleitete Klassen benutzen will (also > Polymorphie). Zeigt ein Basisklassenpointer auf ein Objekt einer > abgeleiteten Klasse und man löscht den Basisklassenpointer mit delete, > wird der Destruktor der abgeleiteten Klasse nur aufgerufen wenn er > virtuell ist. > > Das heißt: Virtueller Destruktor ist sinnvoll, sobald es mind. eine > virtuelle Funktion gibt. Mit dieser zeigt man, dass Polymorphie hier > angedacht ist. Außerdem "bezahlt" man nicht mehr, da es dann bereits > einen vTable gibt. > > Den Destruktor immer virtuell zu machen führt nicht zu Fehlern, kostet > ein paar Byte pro Objekt und man sagt damit etwas fragwürdiges aus wie: > "Benutze meine Klasse nicht polymorph. Falls du es doch tust, habe ich > dir schonmal eine potentielle Fehlerquelle ausgeschaltet." Das ist nicht die einzige Form von Polymorphie in C++, es ist nur eine allgemeine Form von Polymorphie die bei C++ "Überschreibung nichtvirtueller Elementfunktionen" heisst, es gibt z.B. auch noch Überladung und virtuelle Vererbung. Der Rest von Daniels Ansatz ist natürlich richtig. Polymorphie bedeutet allgemein, dass sich ein Objekt/Vorgang je nach Kontext unterschiedlich verhält. Was der TO fragt wirkt sich auf alle Formen von Polymorphie aus, nicht nur auf die hier beschriebene. Außerdem gilt das gleiche Verhalten auch für einen nichtvirtuellen Destruktor in der Basisklasse wenn man die Ableitung zerstört. Das hat auf ganz viele weitere Fälle eine Auswirkung, da hat Daniel auch Scott Meyers in Effective C++ auf seiner Seite. Sobald eine andere virtuelle Methode (Elementfunktion) vorhanden ist sollte der Destruktor auch virtuell sein. Als Erklärung für den TO: Sobald der Kontext nicht mehr die Klasse des nichtvirtuellen Destruktors ist wird dieser nicht mehr aufgerufen. Die Antwort für die Frage des TO ist ganz einfach: Wenn eine Klasse keine virtuellen Mitglieder hat bewirkt ein virtueller Konstruktor einen höheren Resourcenbedarf.
Chris F. schrieb: > Das ist nicht die einzige Form von Polymorphie in C++, es ist nur eine Nur mal so am Rande: C++ ist eine Multiparadigmen-Sprache (das macht sie eben zu einem recht komplexen Werkzeugkasten) und es gibt - Inklusionspolymorphie - parametrische Polymorphie - ad-hoc Polymorphie (aka Funktionsüberladung) - Operator-Polymorphie (in C++ (fast) identisch mit Funktioneüberladung) Wenn man sehr spitzfindig ist, könnte man die Liste ggf. noch weiter unterteilen. Wobei m.E die Form der param. Polymorhie in C++ das mächtigste Werkzeug ist.
:
Bearbeitet durch User
>>Re: C++ virtual destructor
10 Mann, 30 Meinungen.
Bei so einem komplizierten Kram muß man sich nicht wundern, wenn der
Programmschreiber zum REALEN DESTRUKTOR wird und den Rechner zum Fenster
'naus haut.
:))
MfG Paul
Paul B. schrieb: > 10 Mann, 30 Meinungen. Dann noch eine, siehe #4: http://www.gotw.ca/publications/mill18.htm Oliver
Oliver S. schrieb: > Paul B. schrieb: >> 10 Mann, 30 Meinungen. > > Dann noch eine, siehe #4: > > http://www.gotw.ca/publications/mill18.htm > > Oliver Wenn auch nicht die Allerneueste: "This article appeared in C/C++ Users Journal, 19(9), September 2001."
Daniel R. schrieb: > Das heißt: Virtueller Destruktor ist sinnvoll, sobald es mind. eine > virtuelle Funktion gibt. Das ist aber so nicht richtig oder zumindest nicht vollständig. Man nehme eine leere Klasse. Und eine davon abgeleitete Klasse, deren Konstruktor einer abgeleiteten Klasse z.B. Speicher auf dem Heap anfordert und diesen in seinem Destruktor wieder freigibt. Schon dann braucht man in der leeren Klasse einen virtuellen Destruktor. Sonst wird der Destruktor der abgeleiteten Klasse nicht aufgerufen, wenn man über einen Zeiger auf die (leere) Basisklasse operiert.
Ja, bei virtual musst du ihn in jedem Fall überschreiben.. Wenn auch nur eine einzige virtuelle Methode existiert, legt der Compiler den ganzen Verwaltungsaufwand für virtuelle Methoden an.
Beitrag #5193672 wurde vom Autor gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.