Warum brauchen wir in C ++ einen reinen virtuellen Destruktor?


154

Ich verstehe die Notwendigkeit eines virtuellen Destruktors. Aber warum brauchen wir einen reinen virtuellen Destruktor? In einem der C ++ - Artikel hat der Autor erwähnt, dass wir einen reinen virtuellen Destruktor verwenden, wenn wir eine Klasse abstrakt machen möchten.

Wir können eine Klasse jedoch abstrakt machen, indem wir jedes Mitglied als rein virtuell fungieren lassen.

Meine Fragen sind also

  1. Wann machen wir einen Destruktor wirklich virtuell? Kann jemand ein gutes Echtzeitbeispiel geben?

  2. Wenn wir abstrakte Klassen erstellen, ist es eine gute Praxis, den Destruktor auch rein virtuell zu machen? Wenn ja ... warum dann?



14
@ Daniel- Die genannten Links beantworten meine Frage nicht. Es antwortet, warum ein reiner virtueller Destruktor eine Definition haben sollte. Meine Frage ist, warum wir einen reinen virtuellen Destruktor brauchen.
Mark

Ich habe versucht, den Grund herauszufinden, aber Sie haben die Frage hier bereits gestellt.
nsivakr

Antworten:


119
  1. Wahrscheinlich ist der wahre Grund, warum reine virtuelle Destruktoren erlaubt sind, dass das Verbot dieser Destillate das Hinzufügen einer weiteren Regel zur Sprache bedeuten würde und diese Regel nicht erforderlich ist, da das Zulassen eines reinen virtuellen Destruktors keine negativen Auswirkungen haben kann.

  2. Nein, einfache alte virtuelle ist genug.

Wenn Sie ein Objekt mit Standardimplementierungen für seine virtuellen Methoden erstellen und es abstrakt machen möchten, ohne dass jemand gezwungen wird, eine bestimmte Methode zu überschreiben , können Sie den Destruktor rein virtuell machen. Ich sehe nicht viel Sinn darin, aber es ist möglich.

Da der Compiler einen impliziten Destruktor für abgeleitete Klassen generiert, sind abgeleitete Klassen nicht abstrakt , wenn der Autor der Klasse dies nicht tut . Daher macht es für die abgeleiteten Klassen keinen Unterschied, den reinen virtuellen Destruktor in der Basisklasse zu haben. Es wird nur die Basisklasse abstrakt machen (danke für den Kommentar von @kappa ).

Man kann auch annehmen, dass jede abgeleitete Klasse wahrscheinlich einen bestimmten Bereinigungscode haben und den reinen virtuellen Destruktor als Erinnerung verwenden müsste, um einen zu schreiben, aber dies scheint erfunden (und nicht erzwungen) zu sein.

Hinweis: Die destructor ist die einzige Methode, auch wenn es ist rein virtuelle hat eine Implementierung zu haben , um zu instanziieren Klassen abgeleitet (ja rein virtuelle Funktionen können Implementierungen).

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};

13
"Ja, reine virtuelle Funktionen können Implementierungen haben." Dann ist es nicht rein virtuell.
GManNickG

2
Wenn Sie eine Klasse abstrakt machen möchten, wäre es nicht einfacher, alle Konstruktoren zu schützen?
Bdonlan

78
@GMan, Sie irren sich, da reine virtuelle Mittel abgeleitete Klassen diese Methode überschreiben müssen, ist dies orthogonal zu einer Implementierung. Schauen Sie sich meinen Code an und kommentieren Sie, foof::barob Sie sich selbst davon überzeugen möchten.
Motti

15
@GMan: In der C ++ - FAQ-Datei heißt es: "Beachten Sie, dass es möglich ist, eine Definition für eine reine virtuelle Funktion bereitzustellen. Dies verwirrt jedoch normalerweise Anfänger und wird am besten bis später vermieden." parashift.com/c++-faq-lite/abcs.html#faq-22.4 Wikipedia (diese Bastion der Korrektheit) sagt dies ebenfalls. Ich glaube, dass der ISO / IEC-Standard eine ähnliche Terminologie verwendet (leider ist meine Kopie derzeit in Arbeit) ... Ich stimme zu, dass dies verwirrend ist, und ich verwende den Begriff im Allgemeinen nicht ohne Klarstellung, wenn ich eine Definition bereitstelle, insbesondere um neuere Programmierer ...
Leander

9
@Motti: Was hier interessant ist und mehr Verwirrung stiftet, ist, dass der reine virtuelle Destruktor in abgeleiteten (und instanziierten) Klassen NICHT explizit überschrieben werden muss. In einem solchen Fall wird die implizite Definition verwendet :)
Kappa

33

Für eine abstrakte Klasse benötigen Sie lediglich mindestens eine reine virtuelle Funktion. Jede Funktion reicht aus; Aber zufällig ist der Destruktor etwas, das jede Klasse haben wird - also ist er immer als Kandidat da. Darüber hinaus hat die reine Virtualisierung des Destruktors (im Gegensatz zu nur virtuell) keine anderen Verhaltensnebenwirkungen als die Zusammenfassung der Klasse. Aus diesem Grund empfehlen viele Styleguides, den reinen virtuellen Destruktor konsistent zu verwenden, um anzuzeigen, dass eine Klasse abstrakt ist. Wenn dies aus keinem anderen Grund als einem konsistenten Ort geschieht, kann jemand, der den Code liest, nachsehen, ob die Klasse abstrakt ist.


1
aber immer noch, warum die Implementierung des reinen virtuellen Zerstörers bereitzustellen. Was möglicherweise schief gehen könnte Ich mache einen Destruktor rein virtuell und biete seine Implementierung nicht an. Ich gehe davon aus, dass nur Basisklassenzeiger deklariert sind und daher der Destruktor für abstrakte Klassen niemals aufgerufen wird.
Krishna Oza

4
@Surfing: Weil ein Destruktor einer abgeleiteten Klasse implizit den Destruktor seiner Basisklasse aufruft, selbst wenn dieser Destruktor rein virtuell ist. Wenn es also keine Implementierung dafür gibt, wird undefiniertes Verhalten eintreten.
a.peganz

19

Wenn Sie eine abstrakte Basisklasse erstellen möchten:

  • das kann nicht instanziiert werden (yep, das ist überflüssig mit dem Begriff "abstrakt"!)
  • benötigt aber virtuelles Destruktorverhalten (Sie beabsichtigen, Zeiger auf das ABC anstatt Zeiger auf die abgeleiteten Typen herumzutragen und durch diese zu löschen)
  • aber benötigt keine anderen virtuellen Dispatch Verhalten für andere Methoden (vielleicht gibt es keine anderen Methoden? Betrachten wir ein einfaches geschützt „Ressource“ Container, der eine Konstrukteurs / destructor / Zuordnung muss aber nicht viel mehr)

... ist es am einfachsten, die Klasse abstrakt zu machen, indem der Destruktor rein virtuell gemacht und eine Definition (Methodenkörper) dafür bereitgestellt wird.

Für unser hypothetisches ABC:

Sie garantieren, dass es nicht instanziiert werden kann (auch nicht innerhalb der Klasse selbst, aus diesem Grund reichen private Konstruktoren möglicherweise nicht aus), Sie erhalten das virtuelle Verhalten, das Sie für den Destruktor wünschen, und Sie müssen keine andere Methode finden und kennzeichnen, die dies nicht tut Virtueller Versand als "virtuell" nicht erforderlich.


8

Aus den Antworten, die ich auf Ihre Frage gelesen habe, konnte ich keinen guten Grund ableiten, tatsächlich einen reinen virtuellen Destruktor zu verwenden. Zum Beispiel überzeugt mich der folgende Grund überhaupt nicht:

Wahrscheinlich ist der wahre Grund, warum reine virtuelle Destruktoren erlaubt sind, dass das Verbot dieser Destillate das Hinzufügen einer weiteren Regel zur Sprache bedeuten würde und diese Regel nicht erforderlich ist, da das Zulassen eines reinen virtuellen Destruktors keine negativen Auswirkungen haben kann.

Meiner Meinung nach können reine virtuelle Destruktoren nützlich sein. Angenommen, Sie haben zwei Klassen myClassA und myClassB in Ihrem Code und myClassB erbt von myClassA. Aus den von Scott Meyers in seinem Buch "More Effective C ++", Punkt 33 "Nicht-Blattklassen abstrakt machen" genannten Gründen ist es besser, tatsächlich eine abstrakte Klasse myAbstractClass zu erstellen, von der myClassA und myClassB erben. Dies bietet eine bessere Abstraktion und verhindert einige Probleme, die beispielsweise bei Objektkopien auftreten.

Im Abstraktionsprozess (zum Erstellen der Klasse myAbstractClass) kann es sein, dass keine Methode von myClassA oder myClassB ein guter Kandidat für eine reine virtuelle Methode ist (was eine Voraussetzung dafür ist, dass myAbstractClass abstrakt ist). In diesem Fall definieren Sie den Destruktor der abstrakten Klasse rein virtuell.

Im Folgenden ein konkretes Beispiel aus einem Code, den ich selbst geschrieben habe. Ich habe zwei Klassen, Numerics / PhysicsParams, die gemeinsame Eigenschaften haben. Ich lasse sie daher von der abstrakten Klasse IParams erben. In diesem Fall hatte ich absolut keine Methode zur Hand, die rein virtuell sein könnte. Die setParameter-Methode muss beispielsweise für jede Unterklasse denselben Text haben. Die einzige Wahl, die ich hatte, war, den Destruktor von IParams rein virtuell zu machen.

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

1
Ich mag diese Verwendung, aber eine andere Möglichkeit, die Vererbung zu "erzwingen", besteht darin, den Konstruktor IParamals geschützt zu deklarieren , wie in einem anderen Kommentar erwähnt.
rwols

4

Wenn Sie die Instanziierung der Basisklasse beenden möchten, ohne Änderungen an Ihrer bereits implementierten und getesteten Ableitungsklasse vorzunehmen, implementieren Sie einen reinen virtuellen Destruktor in Ihrer Basisklasse.


3

Hier möchte ich sagen, wann wir einen virtuellen Destruktor brauchen und wann wir einen reinen virtuellen Destruktor brauchen

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. Wenn Sie möchten, dass niemand das Objekt der Basisklasse direkt erstellen kann, verwenden Sie einen reinen virtuellen Destruktor virtual ~Base() = 0. Normalerweise ist mindestens eine reine virtuelle Funktion erforderlich, nehmen wir virtual ~Base() = 0als diese Funktion.

  2. Wenn Sie das oben Gesagte nicht benötigen, benötigen Sie nur die sichere Zerstörung des abgeleiteten Klassenobjekts

    Base * pBase = new Derived (); pBase löschen; Ein reiner virtueller Destruktor ist nicht erforderlich, nur der virtuelle Destruktor erledigt die Aufgabe.


2

Mit diesen Antworten geraten Sie in Hypothesen, daher werde ich versuchen, der Klarheit halber eine einfachere, bodenständigere Erklärung abzugeben.

Die grundlegenden Beziehungen des objektorientierten Designs sind zwei: IS-A und HAS-A. Ich habe die nicht erfunden. So heißen sie.

IS-A gibt an, dass ein bestimmtes Objekt in einer Klassenhierarchie zu der darüber liegenden Klasse gehört. Ein Bananenobjekt ist ein Fruchtobjekt, wenn es eine Unterklasse der Fruchtklasse ist. Dies bedeutet, dass überall dort, wo eine Obstklasse verwendet werden kann, eine Banane verwendet werden kann. Es ist jedoch nicht reflexiv. Sie können eine Basisklasse nicht durch eine Basisklasse ersetzen, wenn diese bestimmte Klasse benötigt wird.

Hat-a angegeben, dass ein Objekt Teil einer zusammengesetzten Klasse ist und dass eine Eigentumsbeziehung besteht. In C ++ bedeutet dies, dass es sich um ein Mitgliedsobjekt handelt und daher die Eigentümerklasse verpflichtet ist, es zu entsorgen oder das Eigentum abzugeben, bevor es sich selbst zerstört.

Diese beiden Konzepte sind in Einzelvererbungssprachen leichter zu realisieren als in einem Mehrfachvererbungsmodell wie c ++, aber die Regeln sind im Wesentlichen dieselben. Die Komplikation tritt auf, wenn die Klassenidentität nicht eindeutig ist, z. B. wenn ein Banana-Klassenzeiger an eine Funktion übergeben wird, die einen Fruit-Klassenzeiger verwendet.

Virtuelle Funktionen sind zum einen eine Laufzeitsache. Es ist Teil des Polymorphismus, dass damit entschieden wird, welche Funktion zum Zeitpunkt des Aufrufs im laufenden Programm ausgeführt werden soll.

Das virtuelle Schlüsselwort ist eine Compiler-Direktive zum Binden von Funktionen in einer bestimmten Reihenfolge, wenn Unklarheiten bezüglich der Klassenidentität bestehen. Virtuelle Funktionen befinden sich immer in übergeordneten Klassen (soweit ich weiß) und geben dem Compiler an, dass die Bindung von Elementfunktionen an ihre Namen mit der Unterklassenfunktion zuerst und der übergeordneten Klassenfunktion danach erfolgen soll.

Eine Fruit-Klasse kann eine virtuelle Funktion color () haben, die standardmäßig "NONE" zurückgibt. Die Funktion color () der Bananenklasse gibt "YELLOW" oder "BROWN" zurück.

Aber wenn die Funktion, die einen Fruchtzeiger verwendet, color () für die an sie gesendete Bananenklasse aufruft - welche color () -Funktion wird aufgerufen? Die Funktion ruft normalerweise Fruit :: color () für ein Fruit-Objekt auf.

Das wäre in 99% der Fälle nicht das, was beabsichtigt war. Wenn jedoch Fruit :: color () als virtuell deklariert wurde, wird Banana: color () für das Objekt aufgerufen, da die richtige color () -Funktion zum Zeitpunkt des Aufrufs an den Fruit-Zeiger gebunden wäre. Die Laufzeit überprüft, auf welches Objekt der Zeiger zeigt, da es in der Fruit-Klassendefinition als virtuell markiert wurde.

Dies unterscheidet sich vom Überschreiben einer Funktion in einer Unterklasse. In diesem Fall ruft der Fruit-Zeiger Fruit :: color () auf, wenn er nur weiß, dass es sich um einen IS-A-Zeiger auf Fruit handelt.

Nun kommt also die Idee einer "reinen virtuellen Funktion" auf. Es ist ein ziemlich unglücklicher Satz, da Reinheit nichts damit zu tun hat. Dies bedeutet, dass die Basisklassenmethode niemals aufgerufen werden soll. In der Tat kann eine reine virtuelle Funktion nicht aufgerufen werden. Es muss jedoch noch definiert werden. Eine Funktionssignatur muss vorhanden sein. Viele Codierer erstellen der Vollständigkeit halber eine leere Implementierung {}, aber der Compiler generiert intern eine, wenn nicht. In diesem Fall wird Banana :: color () aufgerufen, wenn die Funktion aufgerufen wird, auch wenn der Zeiger auf Fruit zeigt, da dies die einzige Implementierung von color () ist.

Nun das letzte Puzzleteil: Konstruktoren und Destruktoren.

Reine virtuelle Konstruktoren sind völlig illegal. Das ist einfach raus.

Reine virtuelle Destruktoren funktionieren jedoch in dem Fall, dass Sie die Erstellung einer Basisklasseninstanz verbieten möchten. Nur Unterklassen können instanziiert werden, wenn der Destruktor der Basisklasse rein virtuell ist. Die Konvention besteht darin, es 0 zuzuweisen.

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

In diesem Fall müssen Sie eine Implementierung erstellen. Der Compiler weiß, dass Sie dies tun, und stellt sicher, dass Sie es richtig machen, oder er beschwert sich mächtig, dass er nicht mit allen Funktionen verknüpfen kann, die er zum Kompilieren benötigt. Die Fehler können verwirrend sein, wenn Sie nicht auf dem richtigen Weg sind, wie Sie Ihre Klassenhierarchie modellieren.

In diesem Fall ist es Ihnen also verboten, Instanzen von Obst zu erstellen, Sie dürfen jedoch Instanzen von Bananen erstellen.

Ein Aufruf zum Löschen des Fruchtzeigers, der auf eine Bananeninstanz verweist, ruft zuerst Banana :: ~ Banana () und dann immer Fuit :: ~ Fruit () auf. Denn egal was passiert, wenn Sie einen Unterklassen-Destruktor aufrufen, muss der Basisklassen-Destruktor folgen.

Ist es ein schlechtes Modell? Es ist in der Entwurfsphase zwar komplizierter, kann jedoch sicherstellen, dass zur Laufzeit eine korrekte Verknüpfung durchgeführt wird und dass eine Unterklassenfunktion ausgeführt wird, bei der Unklarheiten darüber bestehen, auf welche Unterklasse genau zugegriffen wird.

Wenn Sie C ++ so schreiben, dass Sie nur exakte Klassenzeiger ohne generische oder mehrdeutige Zeiger weitergeben, werden virtuelle Funktionen nicht wirklich benötigt. Wenn Sie jedoch eine Laufzeitflexibilität der Typen benötigen (wie in Apple Banana Orange ==> Fruit), werden die Funktionen mit weniger redundantem Code einfacher und vielseitiger. Sie müssen nicht mehr für jede Fruchtart eine Funktion schreiben, und Sie wissen, dass jede Frucht auf color () mit ihrer eigenen korrekten Funktion reagiert.

Ich hoffe, diese langatmige Erklärung verfestigt das Konzept und verwirrt die Dinge nicht. Es gibt viele gute Beispiele, die man sich ansehen und genug ansehen und sie tatsächlich ausführen und mit ihnen herumspielen kann, und Sie werden es bekommen.


1

Dies ist ein zehn Jahre altes Thema :) Lesen Sie die letzten 5 Absätze von Punkt 7 des Buches "Effective C ++", um Einzelheiten zu erfahren. Beginnen Sie mit "Gelegentlich kann es praktisch sein, einer Klasse einen reinen virtuellen Destruktor zu geben ...".


0

Sie haben nach einem Beispiel gefragt, und ich glaube, das Folgende liefert einen Grund für einen reinen virtuellen Destruktor. Ich freue mich auf Antworten, ob dies ein guter Grund ist ...

Ich möchte nicht, dass jemand den error_baseTyp auslösen kann , aber die Ausnahmetypen error_oh_shucksund error_oh_blasthaben identische Funktionen und ich möchte ihn nicht zweimal schreiben. Die pImpl-Komplexität ist erforderlich, um eine Exposition std::stringgegenüber meinen Clients zu vermeiden , und die Verwendung von std::auto_ptrerfordert den Kopierkonstruktor.

Der öffentliche Header enthält die Ausnahmespezifikationen, die dem Client zur Verfügung stehen, um verschiedene Arten von Ausnahmen zu unterscheiden, die von meiner Bibliothek ausgelöst werden:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

Und hier ist die gemeinsame Implementierung:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

Die privat gehaltene Klasse exception_string verbirgt std :: string vor meiner öffentlichen Schnittstelle:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

Mein Code gibt dann einen Fehler aus:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

Die Verwendung einer Vorlage für errorist ein wenig unentgeltlich. Es spart ein bisschen Code auf Kosten der Anforderung, dass Clients Fehler abfangen müssen, wie:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

0

Vielleicht gibt es noch einen REAL USE-CASE eines reinen virtuellen Destruktors, den ich in anderen Antworten eigentlich nicht sehen kann :)

Zunächst stimme ich der markierten Antwort voll und ganz zu: Weil das Verbot eines reinen virtuellen Destruktors eine zusätzliche Regel in der Sprachspezifikation erfordern würde. Aber es ist immer noch nicht der Anwendungsfall, den Mark fordert :)

Stellen Sie sich zunächst Folgendes vor:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

und so etwas wie:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

Einfach - wir haben eine Schnittstelle Printableund einen "Container", der alles mit dieser Schnittstelle enthält. Ich denke hier ist ziemlich klar, warum die print()Methode rein virtuell ist. Es könnte einen Körper haben, aber falls es keine Standardimplementierung gibt, ist pure virtual eine ideale "Implementierung" (= "muss von einer untergeordneten Klasse bereitgestellt werden").

Und stellen Sie sich jetzt genau dasselbe vor, außer dass es nicht zum Drucken, sondern zur Zerstörung dient:

class Destroyable {
  virtual ~Destroyable() = 0;
};

Und es könnte auch einen ähnlichen Container geben:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

Es ist ein vereinfachter Anwendungsfall aus meiner realen Anwendung. Der einzige Unterschied besteht darin, dass anstelle von "normal" die "spezielle" Methode (Destruktor) verwendet wurde print(). Der Grund, warum es rein virtuell ist, ist immer noch der gleiche - es gibt keinen Standardcode für die Methode. Ein bisschen verwirrend könnte die Tatsache sein, dass es einen Destruktor effektiv geben MUSS und der Compiler tatsächlich einen leeren Code dafür generiert. Aus Sicht eines Programmierers bedeutet reine Virtualität jedoch immer noch: "Ich habe keinen Standardcode, er muss von abgeleiteten Klassen bereitgestellt werden."

Ich denke, es ist hier keine große Idee, nur eine Erklärung dafür, dass reine Virtualität wirklich einheitlich funktioniert - auch für Destruktoren.


-2

1) Wenn Sie möchten, dass die abgeleiteten Klassen bereinigt werden. Das ist selten.

2) Nein, aber Sie möchten, dass es virtuell ist.


-2

Wir müssen den Destruktor virtuell machen, weil der Compiler, wenn wir den Destruktor nicht virtuell machen, nur den Inhalt der Basisklasse zerstört, n alle abgeleiteten Klassen unverändert bleiben und der Bacuse-Compiler den Destruktor eines anderen nicht aufruft Klasse außer der Basisklasse.


-1: Die Frage ist nicht, warum ein Destruktor virtuell sein sollte.
Troubadour

Darüber hinaus müssen Destruktoren in bestimmten Situationen nicht virtuell sein, um eine korrekte Zerstörung zu erreichen. Virtuelle Destruktoren werden nur benötigt, wenn Sie am Ende deleteeinen Zeiger auf die Basisklasse aufrufen, obwohl dieser tatsächlich auf seine Ableitung verweist.
CygnusX1

Sie sind 100% korrekt. Dies ist und war in der Vergangenheit eine der Hauptursachen für Lecks und Abstürze in C ++ - Programmen, drittens nur der Versuch, Dinge mit Nullzeigern zu tun und die Grenzen von Arrays zu überschreiten. Ein nicht virtueller Basisklassen-Destruktor wird auf einem generischen Zeiger aufgerufen, wobei der Unterklassen-Destruktor vollständig umgangen wird, wenn er nicht als virtuell markiert ist. Wenn dynamisch erstellte Objekte zur Unterklasse gehören, werden diese bei einem Aufruf zum Löschen vom Basis-Destruktor nicht wiederhergestellt. Sie tuckern gut, dann BLUURRK! (Schwer zu finden, wo auch.)
Chris Reid
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.