Разница возникает при удалении объектов через указатель. Если деструктор не виртуальный, то будет вызван деструктор того типа, какой заявлен в указателе. Если деструктор виртуальный, то будет вызван деструктор того типа, которому принадлежит фактический объект.
Пример. Создадим пару классов. В первой паре, ANonVirt и BNonVirt - деструктор не виртуальный. Во второй паре AVirt и BVirt используется виртуальный деструктор.
#include <iostream>
using namespace std;
class ANonVirt {
public:
~ANonVirt() { cerr << "ANonVirt" << endl; }
};
class BNonVirt : public ANonVirt {
public:
~BNonVirt() { cerr << "BNonVirt" << endl; }
};
class AVirt {
public:
virtual ~AVirt() { cerr << "AVirt" << endl; }
};
class BVirt : public AVirt {
public:
virtual ~BVirt() { cerr << "BVirt" << endl; }
};
Теперь подготовим два тестовых примера. Первый пример создаёт автоматические переменные и удаляет их. Второй пример создаёт указатели на класс-потомок и присваивает его указателю на базовый класс (полиморфизм).
void test_local_objects() {
{
cerr << "+++ BNonVirt bnv;" << endl;
BNonVirt bnv;
cerr << "+++ Destroy bnv" << endl;
}
{
cerr << "+++ BVirt bv;" << endl;
BVirt bv;
cerr << "+++ Destroy bv" << endl;
}
}
void test_pointers() {
cerr << "+++ ANonVirt * p_bnv = new BNonVirt()" << endl;
ANonVirt * p_bnv = new BNonVirt();
cerr << "+++ Delete p_bnv" << endl;
delete p_bnv;
cerr << "+++ AVirt * p_bv = new BVirt()" << endl;
AVirt * p_bv = new BVirt();
cerr << "+++ Delete p_bv" << endl;
delete p_bv;
}
Если примеры выполнить, например так
int main() {
cerr << "Testing local objects" << endl << endl;
test_local_objects();
cerr << endl << "Testing pointers" << endl << endl;
test_pointers();
return 0;
}
то получится вот что.
Для автоматических объектов трасса событий такая:
+++ BNonVirt bnv;
+++ Destroy bnv
BNonVirt
ANonVirt
+++ BVirt bv;
+++ Destroy bv
BVirt
AVirt
Действительно, в обоих случаях вызываются как деструктор потомка, так и деструктор предка.
Теперь посмотрим, что получилось для виртуальных деструкторов.
+++ ANonVirt * p_bnv = new BNonVirt()
+++ Delete p_bnv
ANonVirt
+++ AVirt * p_bv = new BVirt()
+++ Delete p_bv
BVirt
AVirt
Нетрудно заметить, что для указателя p_bnv на класс с невиртуальным деструктором вызывается только деструктор базового класса, так как формальный тип указателя ANonVirt*. Так как деструктор не виртуальный, то генерируется код удаления объекта, который просто вызывает деструктор из таблицы методов типа ANonVirt.
Для указателя p_bv на тип с виртуальным деструктором вызываются оба деструктора, так как компилятор "помнит", что указатель привязан к объекту. Для удаления такого объекта генерируется код, который берёт указатель на метод деструктора из таблицы виртуальных функций самого объекта, а не типа AVirt. Тот, в свою очередь, "знает" о деструкторе предка и, соответственно, вызывает предка, и так далее до самого первого предка.
Виртуальные деструкторы необходимы в тех случаях, когда используются полиморфные указатели.