Виртуальный деструктор в с ++

В приведенном ниже коде почему ~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, а затем пробирается вниз по стеку.

Другие вопросы по тегам