webentwicklung-frage-antwort-db.com.de

Warum sollte ich in C ++ einen virtuellen Destruktor für eine abstrakte Klasse deklarieren?

Ich weiß, dass es eine gute Praxis ist, virtuelle Destruktoren für Basisklassen in C++ zu deklarieren, aber ist es immer wichtig, virtual Destruktoren zu deklarieren, auch für abstrakte Klassen, die als Schnittstellen fungieren? Bitte geben Sie einige Gründe und Beispiele dafür an.

157
Kevin

Es ist noch wichtiger für eine Schnittstelle. Jeder Benutzer Ihrer Klasse wird wahrscheinlich einen Zeiger auf die Schnittstelle haben, keinen Zeiger auf die konkrete Implementierung. Wenn der Destruktor nicht virtuell ist und gelöscht werden soll, wird der Destruktor der Schnittstelle (oder der vom Compiler bereitgestellte Standardwert, falls Sie keinen angegeben haben) und nicht der Destruktor der abgeleiteten Klasse aufgerufen. Instant Memory Leak.

Beispielsweise

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}
183
Airsource Ltd

Die Antwort auf Ihre Frage ist oft, aber nicht immer. Wenn Ihre abstrakte Klasse Clients verbietet, delete für einen Zeiger darauf aufzurufen (oder wenn dies in der Dokumentation angegeben ist), können Sie keinen virtuellen Destruktor deklarieren.

Sie können Clients verbieten, delete für einen Zeiger darauf aufzurufen, indem Sie dessen Destruktor schützen. Wenn Sie so arbeiten, ist es absolut sicher und vernünftig, einen virtuellen Destruktor wegzulassen.

Sie werden schließlich keine virtuelle Methodentabelle haben und Ihren Kunden signalisieren, dass Sie beabsichtigen, sie durch einen Zeiger nicht löschbar zu machen. In diesen Fällen haben Sie also in der Tat Grund, sie nicht als virtuell zu deklarieren.

[Siehe Punkt 4 in diesem Artikel: http://www.gotw.ca/publications/mill18.htm ]

Ich habe mich dazu entschlossen, nachzuforschen und zu versuchen, Ihre Antworten zusammenzufassen. Die folgenden Fragen helfen Ihnen bei der Entscheidung, welche Art von Destruktor Sie benötigen:

  1. Soll Ihre Klasse als Basisklasse verwendet werden?
    • Nein: Deklarieren Sie einen öffentlichen, nicht virtuellen Destruktor, um V-Pointer für jedes Objekt der Klasse zu vermeiden *.
    • Ja: Lesen Sie die nächste Frage.
  2. Ist Ihre Basisklasse abstrakt? (d. h. irgendwelche virtuellen reinen Methoden?)
    • Nein: Versuchen Sie, Ihre Basisklasse durch Neugestaltung der Klassenhierarchie abstrakt zu machen
    • Ja: Lesen Sie die nächste Frage.
  3. Möchten Sie das polymorphe Löschen durch einen Basiszeiger zulassen?
    • Nein: Deklarieren Sie geschützten virtuellen Destruktor, um die unerwünschte Verwendung zu verhindern.
    • Ja: Deklarieren Sie den öffentlichen virtuellen Destruktor (in diesem Fall kein Overhead).

Ich hoffe das hilft.

* Es ist wichtig zu beachten, dass es in C++ keine Möglichkeit gibt, eine Klasse als endgültig (dh nicht unterklassifizierbar) zu kennzeichnen. Wenn Sie sich entscheiden, Ihren Destruktor als nicht virtuell und öffentlich zu deklarieren, sollten Sie Ihre Programmierkollegen ausdrücklich davor warnen, eine Klasse abzuleiten aus deiner Klasse.

Verweise:

23
David Alfonso

Ja, es ist immer wichtig. Abgeleitete Klassen können Speicher zuordnen oder Verweise auf andere Ressourcen enthalten, die bereinigt werden müssen, wenn das Objekt zerstört wird. Wenn Sie Ihren Interfaces/abstrakten Klassen keine virtuellen Destruktoren zuweisen, wird der Destruktor Ihrer abgeleiteten Klasse nicht aufgerufen, wenn Sie eine abgeleitete Klasseninstanz über ein Basisklassenhandle löschen.

Daher eröffnen Sie das Potenzial für Speicherverluste

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
7
OJ.

Es ist nicht immer erforderlich, aber ich finde es eine gute Übung. Es ermöglicht das sichere Löschen eines abgeleiteten Objekts über einen Zeiger eines Basistyps.

Also zum Beispiel:

Base *p = new Derived;
// use p as you see fit
delete p;

ist schlecht geformt, wenn Base keinen virtuellen Destruktor hat, weil es versucht, das Objekt zu löschen, als wäre es ein Base *.

7
Evan Teran

Es ist nicht nur eine gute Übung. Es ist Regel Nr. 1 für jede Klassenhierarchie.

  1. Die unterste Klasse einer Hierarchie in C++ muss einen virtuellen Destruktor haben

Nun zum Warum. Nehmen Sie die typische Tierhierarchie. Virtuelle Destruktoren durchlaufen wie jeder andere Methodenaufruf den virtuellen Versand. Nehmen Sie das folgende Beispiel.

Animal* pAnimal = GetAnimal();
delete pAnimal;

Angenommen, Animal ist eine abstrakte Klasse. Die einzige Möglichkeit, mit der C++ den richtigen Destruktor zum Aufrufen kennt, ist der Versand virtueller Methoden. Wenn der Destruktor nicht virtuell ist, ruft er einfach den Destruktor von Animal auf und zerstört keine Objekte in abgeleiteten Klassen.

Der Grund dafür, dass der Destruktor in der Basisklasse virtuell gemacht wird, besteht darin, dass die Auswahl aus abgeleiteten Klassen einfach entfernt wird. Ihr Destruktor wird standardmäßig virtuell.

5
JaredPar

Die Antwort ist einfach: Sie müssen virtuell sein, sonst wäre die Basisklasse keine vollständige polymorphe Klasse.

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

Sie würden die obige Löschung bevorzugen, aber wenn der Destruktor der Basisklasse nicht virtuell ist, wird nur der Destruktor der Basisklasse aufgerufen und alle Daten in der abgeleiteten Klasse bleiben erhalten.

3
fatma.ekici