Ich habe ein solides Verständnis für die meisten OO-Theorien, aber das einzige, was mich sehr verwirrt, sind virtuelle Destruktoren.
Ich dachte, dass der Destruktor immer aufgerufen wird, egal was und für jedes Objekt in der Kette.
Wann sollen sie virtuell gemacht werden und warum?
virtual
stellt sicher, dass es oben statt in der Mitte beginnt.Antworten:
Virtuelle Destruktoren sind nützlich, wenn Sie möglicherweise eine Instanz einer abgeleiteten Klasse über einen Zeiger auf die Basisklasse löschen:
Hier werden Sie feststellen, dass ich den Destruktor von Base nicht als Destruktor deklariert habe
virtual
. Schauen wir uns nun den folgenden Ausschnitt an:Da Basis Destruktor nicht ist
virtual
undb
ist einBase*
Zeige auf einDerived
Objekt,delete b
hat nicht definiertes Verhalten :In den meisten Implementierungen wird der Aufruf des Destruktors wie jeder nicht virtuelle Code aufgelöst, was bedeutet, dass der Destruktor der Basisklasse aufgerufen wird, jedoch nicht der der abgeleiteten Klasse, was zu einem Ressourcenleck führt.
Zusammenfassend lässt sich sagen, dass Basisklassen immer dann Destruktoren sind,
virtual
wenn sie polymorph manipuliert werden sollen.Wenn Sie das Löschen einer Instanz durch einen Basisklassenzeiger verhindern möchten, können Sie den Basisklassendestruktor geschützt und nicht virtuell machen. Auf diese Weise lässt der Compiler Sie
delete
keinen Basisklassenzeiger aufrufen .In diesem Artikel von Herb Sutter erfahren Sie mehr über Virtualität und den Destruktor der virtuellen Basisklasse .
quelle
Base
undDerived
haben alle automatischen Speichervariablen? dh es gibt keinen "speziellen" oder zusätzlichen benutzerdefinierten Code, der im Destruktor ausgeführt werden kann. Ist es dann in Ordnung, überhaupt keine Destruktoren mehr zu schreiben? Oder hat die abgeleitete Klasse immer noch einen Speicherverlust?Ein virtueller Konstruktor ist nicht möglich, aber ein virtueller Destruktor ist möglich. Lass uns experimentieren .......
Der obige Code gibt Folgendes aus:
Die Konstruktion des abgeleiteten Objekts folgt der Konstruktionsregel, aber wenn wir den "b" -Zeiger (Basiszeiger) löschen, haben wir festgestellt, dass nur der Basisdestruktor aufgerufen wird. Das darf aber nicht passieren. Um das Richtige zu tun, müssen wir den Basis-Destruktor virtuell machen. Nun wollen wir sehen, was im Folgenden passiert:
Die Ausgabe hat sich wie folgt geändert:
Die Zerstörung des Basiszeigers (der eine Zuordnung für das abgeleitete Objekt erfordert!) Folgt also der Zerstörungsregel, dh zuerst der Abgeleiteten, dann der Basis. Auf der anderen Seite gibt es nichts Schöneres als einen virtuellen Konstruktor.
quelle
Deklarieren Sie Destruktoren in polymorphen Basisklassen als virtuell. Dies ist Punkt 7 in Scott Meyers ' Effective C ++ . Meyers geht weiter zusammenfassen , dass , wenn eine Klasse hat jede virtuelle Funktion, sollte es einen virtuellen Destruktor haben, und dass die Klassen nicht - Basisklassen ausgelegt sein oder nicht verwendet werden entworfen polymorph sollte nicht virtuelle Destruktoren deklarieren.
quelle
const Base& = make_Derived();
. In diesem Fall wird der Destruktor desDerived
Wertes aufgerufen, auch wenn er nicht virtuell ist, sodass der durch vtables / vpointers verursachte Overhead gespart wird. Natürlich ist der Umfang ziemlich begrenzt. Andrei Alexandrescu erwähnte dies in seinem Buch Modern C ++ Design .Beachten Sie auch, dass das Löschen eines Basisklassenzeigers, wenn kein virtueller Destruktor vorhanden ist, zu undefiniertem Verhalten führt . Etwas, das ich erst kürzlich gelernt habe:
Wie soll sich das Überschreiben von Löschen in C ++ verhalten?
Ich benutze C ++ seit Jahren und schaffe es immer noch, mich aufzuhängen.
quelle
Machen Sie den Destruktor immer dann virtuell, wenn Ihre Klasse polymorph ist.
quelle
Aufruf des Destruktors über einen Zeiger auf eine Basisklasse
Der Aufruf des virtuellen Destruktors unterscheidet sich nicht von anderen virtuellen Funktionsaufrufen.
Denn
base->f()
der Anruf wird an weitergeleitetDerived::f()
, und es ist dasselbe fürbase->~Base()
- seine übergeordnete Funktion - dieDerived::~Derived()
wird aufgerufen.Gleiches passiert, wenn der Destruktor indirekt aufgerufen wird, z
delete base;
. Diedelete
Anweisung wird aufgerufen, anbase->~Base()
die gesendet wirdDerived::~Derived()
.Abstrakte Klasse mit nicht virtuellem Destruktor
Wenn Sie ein Objekt nicht über einen Zeiger auf seine Basisklasse löschen möchten, ist kein virtueller Destruktor erforderlich. Machen Sie
protected
es einfach so, dass es nicht versehentlich aufgerufen wird:quelle
~Derived()
in allen abgeleiteten Klassen explizit zu deklarieren , auch wenn es nur so ist~Derived() = default
? Oder ist das durch die Sprache impliziert (was das Auslassen sicher macht)?protected
Abschnitt einzufügen oder um sicherzustellen, dass er virtuell ist, indem er verwendetoverride
.Ich denke gerne über Schnittstellen und Implementierungen von Schnittstellen nach. In C ++ ist die Sprachschnittstelle eine reine virtuelle Klasse. Der Destruktor ist Teil der Schnittstelle und wird voraussichtlich implementiert. Daher sollte der Destruktor rein virtuell sein. Wie wäre es mit Konstruktor? Der Konstruktor ist eigentlich nicht Teil der Schnittstelle, da das Objekt immer explizit instanziiert wird.
quelle
virtual
in einer Basisklasse deklariert ist, automatischvirtual
in einer abgeleiteten Klasse deklariert wird, auch wenn dies nicht deklariert ist.Das virtuelle Schlüsselwort für den Destruktor ist erforderlich, wenn verschiedene Destruktoren die richtige Reihenfolge einhalten sollen, während Objekte über den Basisklassenzeiger gelöscht werden. zum Beispiel:
Wenn Ihr Basisklassen-Destruktor virtuell ist, werden Objekte in einer Reihenfolge zerstört (zuerst abgeleitetes Objekt, dann Basis). Wenn Ihr Basisklassendestruktor NICHT virtuell ist, wird nur das Basisklassenobjekt gelöscht (da der Zeiger der Basisklasse "Base * myObj" entspricht). Es kommt also zu einem Speicherverlust für das abgeleitete Objekt.
quelle
Um es einfach zu machen: Der virtuelle Destruktor zerstört die Ressourcen in der richtigen Reihenfolge, wenn Sie einen Basisklassenzeiger löschen, der auf ein abgeleitetes Klassenobjekt zeigt.
quelle
delete
ein Basiszeiger aufgerufen wird, führt dies zu undefiniertem Verhalten.Destruktoren für virtuelle Basisklassen sind "Best Practice" - Sie sollten sie immer verwenden, um (schwer zu erkennende) Speicherlecks zu vermeiden. Mit ihnen können Sie sicher sein, dass alle Destruktoren in der Vererbungskette Ihrer Klassen aufgerufen werden (in der richtigen Reihenfolge). Durch das Erben von einer Basisklasse mithilfe eines virtuellen Destruktors wird der Destruktor der ererbenden Klasse ebenfalls automatisch virtuell (sodass Sie in der Deklaration des erbenden Klassendestruktors nicht erneut "virtuell" eingeben müssen).
quelle
Wenn Sie
shared_ptr
(nur shared_ptr, nicht unique_ptr) verwenden, muss der Destruktor der Basisklasse nicht virtuell sein:Ausgabe:
quelle
virtual
Schlüsselwort könnte Sie vor viel Qual bewahren.Was ist ein virtueller Destruktor oder wie wird ein virtueller Destruktor verwendet?
Ein Klassendestruktor ist eine Funktion mit demselben Namen wie die mit ~ vorangestellte Klasse, die den von der Klasse zugewiesenen Speicher neu zuweist. Warum brauchen wir einen virtuellen Destruktor?
Siehe das folgende Beispiel mit einigen virtuellen Funktionen
Das Beispiel zeigt auch, wie Sie einen Buchstaben in einen oberen oder unteren konvertieren können
Aus dem obigen Beispiel können Sie ersehen, dass der Destruktor für die MakeUpper- und die MakeLower-Klasse nicht aufgerufen wird.
Siehe das nächste Beispiel mit dem virtuellen Destruktor
Der virtuelle Destruktor ruft explizit den am meisten abgeleiteten Laufzeit-Destruktor der Klasse auf, damit er das Objekt ordnungsgemäß löschen kann.
Oder besuchen Sie den Link
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
quelle
wenn Sie den abgeleiteten Klassendestruktor aus der Basisklasse aufrufen müssen. Sie müssen den Destruktor der virtuellen Basisklasse in der Basisklasse deklarieren.
quelle
Ich denke, der Kern dieser Frage betrifft virtuelle Methoden und Polymorphismus, nicht den Destruktor speziell. Hier ist ein klareres Beispiel:
Wird ausgedruckt:
Ohne wird
virtual
es ausgedruckt:Und jetzt sollten Sie verstehen, wann Sie virtuelle Destruktoren verwenden müssen.
quelle
B b{}; A& a{b}; a.foo();
. Das Überprüfen aufNULL
- was sein solltenullptr
- bevordelete
- mit falscher Indendation - ist nicht erforderlich:delete nullptr;
wird als No-Op definiert. Wenn überhaupt, sollten Sie dies vor dem Aufruf überprüft haben->foo()
, da sonst undefiniertes Verhalten auftreten kann, wenn dasnew
irgendwie fehlschlägt.)delete
einenNULL
Zeiger aufzurufen (dh Sie brauchen denif (a != NULL)
Wachmann nicht).Ich dachte, es wäre vorteilhaft, das "undefinierte" Verhalten oder zumindest das "abgestürzte" undefinierte Verhalten zu diskutieren, das beim Löschen durch eine Basisklasse (/ struct) ohne virtuellen Destruktor oder genauer gesagt ohne vtable auftreten kann. Der folgende Code listet einige einfache Strukturen auf (dasselbe gilt für Klassen).
Ich schlage nicht vor, ob Sie virtuelle Destruktoren benötigen oder nicht, obwohl ich im Allgemeinen denke, dass es eine gute Praxis ist, sie zu haben. Ich möchte nur auf den Grund hinweisen, warum Sie möglicherweise abstürzen, wenn Ihre Basisklasse (/ struct) keine vtable und Ihre abgeleitete Klasse (/ struct) hat und Sie ein Objekt über eine Basisklasse (/ struct) löschen. Zeiger. In diesem Fall ist die Adresse, die Sie an die freie Routine des Heapspeichers übergeben, ungültig und somit der Grund für den Absturz.
Wenn Sie den obigen Code ausführen, sehen Sie deutlich, wann das Problem auftritt. Wenn sich dieser Zeiger der Basisklasse (/ struct) von diesem Zeiger der abgeleiteten Klasse (/ struct) unterscheidet, tritt dieses Problem auf. Im obigen Beispiel haben Struktur a und b keine vtables. Die Strukturen c und d haben vtables. Somit wird ein a- oder b-Zeiger auf eine ac- oder d-Objektinstanz festgelegt, um die vtable zu berücksichtigen. Wenn Sie diesen a- oder b-Zeiger zum Löschen übergeben, stürzt er ab, da die Adresse für die freie Routine des Heaps ungültig ist.
Wenn Sie abgeleitete Instanzen mit vtables aus Basisklassenzeigern löschen möchten, müssen Sie sicherstellen, dass die Basisklasse über eine vtable verfügt. Eine Möglichkeit, dies zu tun, besteht darin, einen virtuellen Destruktor hinzuzufügen, mit dem Sie möglicherweise ohnehin die Ressourcen ordnungsgemäß bereinigen möchten.
quelle
Eine grundlegende Definition
virtual
ist, dass bestimmt wird, ob eine Elementfunktion einer Klasse in ihren abgeleiteten Klassen überschrieben werden kann.Der D-Tor einer Klasse wird grundsätzlich am Ende des Bereichs aufgerufen. Es gibt jedoch ein Problem. Wenn wir beispielsweise eine Instanz auf dem Heap definieren (dynamische Zuordnung), sollten Sie sie manuell löschen.
Sobald der Befehl ausgeführt wird, wird der Basisklassen-Destruktor aufgerufen, jedoch nicht für den abgeleiteten.
Ein praktisches Beispiel ist, wenn Sie im Kontrollfeld Effektoren und Aktoren manipulieren müssen.
Wenn am Ende des Bereichs der Destruktor eines der Leistungselemente (Aktuator) nicht aufgerufen wird, hat dies fatale Konsequenzen.
quelle
Jede Klasse, die öffentlich vererbt wird, polymorph oder nicht, sollte einen virtuellen Destruktor haben. Anders ausgedrückt, wenn ein Basisklassenzeiger darauf zeigen kann, sollte seine Basisklasse einen virtuellen Destruktor haben.
Wenn virtuell, wird der abgeleitete Klassendestruktor aufgerufen, dann der Basisklassenkonstruktor. Wenn nicht virtuell, wird nur der Basisklassen-Destruktor aufgerufen.
quelle