Ответ
Виртуальный деструктор нужен для корректного и полного разрушения объекта производного класса при его удалении через указатель на базовый класс. Без него может произойти утечка ресурсов или неопределенное поведение.
Проблема:
Если деструктор базового класса не виртуальный, то цепочка вызовов деструкторов при delete через указатель на базовый класс определяется статическим типом указателя, а не динамическим типом объекта. В результате вызывается только деструктор базового класса.
Решение:
Объявление деструктора базового класса как virtual гарантирует, что вызов пойдет по цепочке наследования, начиная с деструктора самого производного класса.
Практический пример с ресурсами:
#include <iostream>
// БАЗОВЫЙ КЛАСС БЕЗ виртуального деструктора (ПЛОХО)
class BaseBad {
public:
BaseBad() { std::cout << "BaseBad constructorn"; }
~BaseBad() { std::cout << "BaseBad destructorn"; } // НЕ ВИРТУАЛЬНЫЙ!
};
// БАЗОВЫЙ КЛАСС С виртуальным деструктором (ХОРОШО)
class BaseGood {
public:
BaseGood() { std::cout << "BaseGood constructorn"; }
virtual ~BaseGood() { std::cout << "BaseGood destructorn"; } // ВИРТУАЛЬНЫЙ!
};
class Derived : public BaseBad, public BaseGood {
int* resource;
public:
Derived() : resource(new int(100)) {
std::cout << "Derived constructor (allocated resource)n";
}
~Derived() {
delete resource; // Освобождение ресурса
std::cout << "Derived destructor (freed resource)n";
}
};
int main() {
std::cout << "=== Deleting via BaseBad pointer (Undefined Behavior) ===n";
BaseBad* badPtr = new Derived();
delete badPtr; // ВЫЗОВЕТСЯ ТОЛЬКО ~BaseBad()! Ресурс не освободится.
std::cout << "nResource leak!nn";
std::cout << "=== Deleting via BaseGood pointer (Correct) ===n";
BaseGood* goodPtr = new Derived();
delete goodPtr; // Вызовется ~Derived(), затем ~BaseGood().
std::cout << "nAll resources properly freed.n";
return 0;
}
Вывод из примера (примерный):
=== Deleting via BaseBad pointer (Undefined Behavior) ===
BaseBad constructor
BaseGood constructor
Derived constructor (allocated resource)
BaseBad destructor // УТЕЧКА! Ресурс не освобожден.
Resource leak!
=== Deleting via BaseGood pointer (Correct) ===
BaseBad constructor
BaseGood constructor
Derived constructor (allocated resource)
Derived destructor (freed resource) // Ресурс освобожден.
BaseGood destructor
BaseBad destructor
All resources properly freed.
Правило: Если класс предназначен для полиморфного использования (т.е. через указатель/ссылку на базовый класс могут создаваться объекты производных классов), деструктор базового класса должен быть виртуальным. Для классов, не предназначенных для наследования, можно объявить деструктор как protected невиртуальный или использовать final в C++11.