Я подумал, что было бы полезно обсудить поведение «undefined» или, по крайней мере, поведение undefined «сбой», которое может произойти при удалении через базовый класс (/ struct) без виртуального деструктора или, точнее, без vtable. В приведенном ниже коде перечислено несколько простых структур (то же самое верно и для классов).
#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;
*/
}
Я не говорю, нужны ли вам виртуальные деструкторы или нет, хотя я думаю, что в целом иметь их - это хорошая практика. Я просто указываю причину, по которой у вас может произойти сбой, если ваш базовый класс (/ struct) не имеет vtable, а ваш производный класс (/ struct) имеет, и вы удаляете объект через базовый класс (/ struct) указатель. В этом случае адрес, который вы передаете свободной подпрограмме кучи, недействителен и, следовательно, является причиной сбоя.
Если вы запустите приведенный выше код, вы четко увидите, когда возникнет проблема. Когда указатель this базового класса (/ struct) отличается от указателя this производного класса (/ struct), вы столкнетесь с этой проблемой. В приведенном выше примере структуры a и b не имеют vtables. в структурах c и d есть таблицы vtables. Таким образом, указатель a или b на экземпляр объекта c или d будет зафиксирован для учета vtable. Если вы передадите этот указатель a или b для удаления, он выйдет из строя из-за того, что адрес недействителен для бесплатной процедуры кучи.
Если вы планируете удалить производные экземпляры, которые имеют vtables из указателей базового класса, вам необходимо убедиться, что у базового класса есть vtable. Один из способов сделать это - добавить виртуальный деструктор, который в любом случае может понадобиться для правильной очистки ресурсов.
person
nickdu
schedule
23.03.2017
virtual
следит за тем, чтобы он начинался сверху, а не посередине. - person Mooing Duck   schedule 29.06.2013