Виртуальный деструктор в с ++
В приведенном ниже коде почему ~Derived()
деструктор вызывается автоматически?
#include<iostream>
using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << "Calling ~Base()" << endl;
}
};
class Derived: public Base
{
private:
int* m_pnArray;
public:
Derived(int nLength)
{
m_pnArray = new int[nLength];
}
virtual ~Derived()
{
cout << "Calling ~Derived()" << endl;
delete[] m_pnArray;
}
};
int main()
{
Derived *pDerived = new Derived(5);
Base *pBase = pDerived;
delete pBase;
return 0;
}
4 ответа
Потому что ваш деструктор базового класса является виртуальным
virtual ~Base();
вызов delete для указателя на базовый класс приводит к виртуальному вызову деструктора и, как и любой виртуальный вызов, отправляется соответствующей функции в производном классе. Это не только хорошо, но и необходимо: иначе поведение не определено.
Это важно для производных классов, деструктор которых не является пустой функцией. В противном случае не виртуальный вызов приведет к вызову деструктора базового класса, утечке производных ресурсов и т. Д.
Когда у вас есть хотя бы один virtual
функции в классе, то компилятор создает одну таблицу для класса, в которой перечислены указатели на функции-члены. Рассматривать:
struct Base
{
virtual ~Base() { };
int n_;
};
В псевдокоде вы можете представить компилятор, добавляющий:
void* Base::__virtual_dispatch_table[] = { (void*)&Base::~Base };
Затем, когда у вас есть фактический объект типа Base
у него будет дополнительный скрытый элемент данных, который указывает на Base::__virtual_dispatch_table
("ВДТ"):
Variable definition Memory layout
------------------- -------------
Base myBase; int n_;
void** __p_vdt = Base::__virtual_dispatch_table;
Теперь, если у вас есть Base* p
а также delete p;
компилятор говорит "эй - это virtual
- Я не буду жестко кодировать звонок Base::~Base
вместо этого я сгенерирую код, который делает что-то вроде этого псевдокода:
void (Base::*p_destructor) = p->__p_vdt[0]
*p_destructor(p); // "p" will provide the "this" value while the destructor runs
Зачем тебе все это делать? Потому что, когда вы приходите с Derived
объект...
class Derived: public Base
{
private:
int* m_pnArray;
...
... компилятор может создать отдельную виртуальную таблицу диспетчеризации...
void* Derived::__virtual_dispatch_table[] = { (void*)&Derived::~Derived };
... и выложить память производного объекта следующим образом:
Variable definition Memory layout
------------------- -------------
Derived derived; int n_;
void** __p_vdt = Derived::__virtual_dispatch_table;
int* m_pnArray;
Обратите внимание, что __p_vdt
находится в том же относительном месте в макете объекта, но теперь указывает на Derived
таблица виртуальной отправки класса?
Теперь, если вы создаете Base*
в derived
точно такой же код, необходимый для вызова деструктора Base
объект, который - в случае, если вы потеряли трек - был...
void (Base::*p_destructor) = p->__p_vdt[0]
*p_destructor(p); // "p" will provide the "this" value while the destructor runs
... можно запустить, но в конечном итоге использовать Derived
объекты __p_vdt
ценность Derived::__virtual_dispatch_table
и найти Derived
деструктор класса.
Потому что это позволяет лечить любой Base
объект (который на самом деле может быть Derived
) как объект, который вы можете удалить.
В этом случае, если delete pBase
не звонил Derived
деструктор, данные, хранящиеся m_pnArray
никогда не будет удален, то есть произойдет "утечка памяти".
Когда вы звоните
delete pBase;
Он просматривает таблицу виртуальных функций pBase, чтобы найти подходящий деструктор, с которого нужно начать разматывание, и находит Derived::~Derived, а затем пробирается вниз по стеку.