Forum: PC-Programmierung C++ virtual destructor


von Maier (Gast)


Lesenswert?

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?

von Noch einer (Gast)


Lesenswert?

Höherer Speicherbedarf, mehr Laufzeit.

Wenn auch nur eine einzige virtuelle Methode existiert, legt der 
Compiler den ganzen Verwaltungsaufwand für virtuelle Methoden an.

von fauler Sack (Gast)


Lesenswert?

Ja, bei virtual musst du ihn in jedem Fall überschreiben.

von jz23 (Gast)


Lesenswert?

fauler Sack schrieb:
> Ja, bei virtual musst du ihn in jedem Fall überschreiben.

Verwechselt du gerade virtual und abstrakt? Virtual muss man nicht 
überschreiben.

von fauler Sack (Gast)


Lesenswert?

VMT wird bei einer Vererbung immer angelegt

von Dr. Sommer (Gast)


Lesenswert?

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.

von fauler Sack (Gast)


Lesenswert?

Ja, ja, bin wohl noch nicht richtig wach.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Noch einer (Gast)


Lesenswert?

>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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

... 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.

von Noch einer (Gast)


Lesenswert?

>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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

... und wenn die lib dann ein delete auf den Pointer ausführt, knallt 
es. Nur deshalb braucht es ja den virtuellen Destruktor.

Oliver

von Dr. Sommer (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Noch einer (Gast)


Lesenswert?

>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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Daniel R. (dan066)


Lesenswert?

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."

von Peter II (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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).

von mh (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Chris F. (chfreund) Benutzerseite


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
von Paul B. (paul_baumann)


Lesenswert?

>>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

von Oliver S. (oliverso)


Lesenswert?

Paul B. schrieb:
> 10 Mann, 30 Meinungen.

Dann noch eine, siehe #4:

http://www.gotw.ca/publications/mill18.htm

Oliver

von Carl D. (jcw2)


Lesenswert?

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."

von Oliver S. (oliverso)


Lesenswert?

Je nun. Manche Erkenntnisse veralten nicht.

Oliver

von Nase (Gast)


Lesenswert?

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.

von Hào N. (ho_n)


Lesenswert?

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
Noch kein Account? Hier anmelden.