Antworten:
Es ist noch wichtiger für eine Schnittstelle. Jeder Benutzer Ihrer Klasse wird wahrscheinlich einen Zeiger auf die Schnittstelle halten, keinen Zeiger auf die konkrete Implementierung. Wenn sie zum Löschen kommen und der Destruktor nicht virtuell ist, rufen sie den Destruktor der Schnittstelle (oder den vom Compiler bereitgestellten Standard, falls Sie keinen angegeben haben) und nicht den Destruktor der abgeleiteten Klasse auf. Sofortiger Speicherverlust.
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;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Es wäre immer noch undefiniert, wenn Derived einen implizit generierten Destruktor verwenden würde.
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 mehr haben und Ihren Clients Ihre Absicht signalisieren, sie durch einen Zeiger darauf nicht löschbar zu machen. Sie haben also in der Tat Grund, sie in diesen Fällen nicht als virtuell zu deklarieren.
[Siehe Punkt 4 in diesem Artikel: http://www.gotw.ca/publications/mill18.htm ]
Ich habe mich entschlossen, etwas zu recherchieren und zu versuchen, Ihre Antworten zusammenzufassen. Die folgenden Fragen helfen Ihnen bei der Entscheidung, welche Art von Destruktor Sie benötigen:
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 markieren. Wenn Sie sich entscheiden, Ihren Destruktor als nicht virtuell und öffentlich zu deklarieren, denken Sie daran, Ihre Programmierkollegen ausdrücklich davor zu warnen von Ihrer Klasse abgeleitet.
Verweise:
Ja, das ist immer wichtig. Abgeleitete Klassen können Speicher zuweisen oder Verweise auf andere Ressourcen enthalten, die bereinigt werden müssen, wenn das Objekt zerstört wird. Wenn Sie Ihren Schnittstellen / abstrakten Klassen keine virtuellen Destruktoren geben, wird jedes Mal, wenn Sie eine abgeleitete Klasseninstanz über ein Basisklassenhandle löschen, der Destruktor Ihrer abgeleiteten Klasse nicht aufgerufen.
Daher eröffnen Sie das Potenzial für Speicherlecks
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
Es ist nicht immer erforderlich, aber ich finde es eine gute Praxis. Damit kann ein abgeleitetes Objekt sicher über einen Zeiger eines Basistyps gelöscht werden.
Also zum Beispiel:
Base *p = new Derived;
// use p as you see fit
delete p;
ist schlecht geformt, wenn Base
kein virtueller Destruktor vorhanden ist, da versucht wird, das Objekt so zu löschen, als wäre es ein Base *
.
shared_ptr
wird versucht, das Objekt so zu löschen, als wäre es ein Base *
- es merkt sich den Typ des Objekts, mit dem Sie es erstellt haben. Siehe den Link, auf den verwiesen wird, insbesondere das Bit "Der Destruktor ruft delete mit demselben Zeiger auf, einschließlich seines ursprünglichen Typs, auch wenn T keinen virtuellen Destruktor hat oder ungültig ist."
Es ist nicht nur eine gute Praxis. Es ist Regel Nr. 1 für jede Klassenhierarchie.
Nun zum Warum. Nehmen Sie die typische Tierhierarchie. Virtuelle Destruktoren durchlaufen den virtuellen Versand wie jeder andere Methodenaufruf. Nehmen Sie das folgende Beispiel.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Angenommen, Animal ist eine abstrakte Klasse. C ++ kennt den richtigen Destruktor nur über den 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, den Destruktor in der Basisklasse virtuell zu machen, besteht darin, dass die Auswahl einfach aus abgeleiteten Klassen entfernt wird. Ihr Destruktor wird standardmäßig virtuell.
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 das obige Löschen 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 nicht gelöscht.
delete p
ruft undefiniertes Verhalten auf. Es ist nicht garantiert anzurufenInterface::~Interface
.