Ich dachte, es wäre von Vorteil, das "undefinierte" Verhalten zu diskutieren, oder zumindest den "Absturz" undefiniertes Verhalten, das beim Löschen durch eine Basisklasse (/struct) ohne einen virtuellen Destruktor, oder genauer gesagt keine vtable auftreten kann. Der folgende Code listet einige einfache Structs auf (dasselbe gilt für Klassen).
#include <iostream>
using namespace std;
struct a
{
~a() {}
unsigned long long i;
};
struct b : a
{
~b() {}
unsigned long long j;
};
struct c : b
{
~c() {}
virtual void m3() {}
unsigned long long k;
};
struct d : c
{
~d() {}
virtual void m4() {}
unsigned long long l;
};
int main()
{
cout << "sizeof(a): " << sizeof(a) << endl;
cout << "sizeof(b): " << sizeof(b) << endl;
cout << "sizeof(c): " << sizeof(c) << endl;
cout << "sizeof(d): " << sizeof(d) << endl;
// No issue.
a* a1 = new a();
cout << "a1: " << a1 << endl;
delete a1;
// No issue.
b* b1 = new b();
cout << "b1: " << b1 << endl;
cout << "(a*) b1: " << (a*) b1 << endl;
delete b1;
// No issue.
c* c1 = new c();
cout << "c1: " << c1 << endl;
cout << "(b*) c1: " << (b*) c1 << endl;
cout << "(a*) c1: " << (a*) c1 << endl;
delete c1;
// No issue.
d* d1 = new d();
cout << "d1: " << d1 << endl;
cout << "(c*) d1: " << (c*) d1 << endl;
cout << "(b*) d1: " << (b*) d1 << endl;
cout << "(a*) d1: " << (a*) d1 << endl;
delete d1;
// Doesn't crash, but may not produce the results you want.
c1 = (c*) new d();
delete c1;
// Crashes due to passing an invalid address to the method which
// frees the memory.
d1 = new d();
b1 = (b*) d1;
cout << "d1: " << d1 << endl;
cout << "b1: " << b1 << endl;
delete b1;
/*
// This is similar to what's happening above in the "crash" case.
char* buf = new char[32];
cout << "buf: " << (void*) buf << endl;
buf += 8;
cout << "buf after adding 8: " << (void*) buf << endl;
delete buf;
*/
}
Ich schlage nicht vor, ob Sie virtuelle Destruktoren brauchen oder nicht, obwohl ich denke, dass es im Allgemeinen eine gute Praxis ist, sie zu haben. Ich weise nur auf den Grund hin, warum es zu einem Absturz kommen kann, wenn Ihre Basisklasse(/struct) keine vtable hat und Ihre abgeleitete Klasse(/struct) schon und Sie ein Objekt über einen Zeiger der Basisklasse(/struct) löschen. In diesem Fall ist die Adresse, die Sie an die Free-Routine des Heaps übergeben, ungültig und somit der Grund für den Absturz.
Wenn Sie den obigen Code ausführen, werden Sie deutlich sehen, wann das Problem auftritt. Wenn der this-Zeiger der Basisklasse (/struct) nicht mit dem this-Zeiger der abgeleiteten Klasse (/struct) übereinstimmt, wird dieses Problem auftreten. In dem obigen Beispiel haben struct a und b keine vtables. structs c und d haben vtables. Daher wird ein a- oder b-Zeiger auf eine c- oder d-Objektinstanz so angepasst, dass die vtable berücksichtigt wird. Wenn Sie diesen a- oder b-Zeiger zum Löschen übergeben, kommt es zum Absturz, da die Adresse für die Free-Routine des Heaps ungültig ist.
Wenn Sie vorhaben, abgeleitete Instanzen, die vtables haben, aus Zeigern der Basisklasse zu löschen, müssen Sie sicherstellen, dass die Basisklasse eine vtable hat. Eine Möglichkeit, dies zu tun, besteht darin, einen virtuellen Destruktor hinzuzufügen, den Sie möglicherweise ohnehin benötigen, um Ressourcen ordnungsgemäß aufzuräumen.