Ответ
При удалении объекта через указатель на базовый класс, если деструктор базового класса объявлен как virtual, гарантируется правильный порядок вызова деструкторов в иерархии наследования:
- Деструктор производного класса.
- Деструктор базового класса.
Почему это важно? Без виртуального деструктора вызовется только деструктор базового класса, что приведёт к неопределённому поведению (UB) и утечке ресурсов, выделенных в производном классе.
Пример на C++:
#include <iostream>
class Base {
public:
virtual ~Base() { // Виртуальный деструктор
std::cout << "Base destructor called.n";
}
};
class Derived : public Base {
int* dynamicArray;
public:
Derived() : dynamicArray(new int[100]) {}
~Derived() override {
delete[] dynamicArray; // Освобождение ресурсов производного класса
std::cout << "Derived destructor called.n";
}
};
int main() {
Base* ptr = new Derived(); // Указатель базового типа на объект Derived
delete ptr; // Благодаря virtual ~Base() вызовется цепочка деструкторов
return 0;
}
Вывод программы:
Derived destructor called.
Base destructor called.
Таким образом, виртуальный деструктор обеспечивает корректное и полное разрушение объекта производного класса при работе с полиморфными иерархиями.
Ответ 18+ 🔞
Э, слушай, вот история про то, как накосячить можно капитально, если в базовом классе деструктор не виртуальный объявить. Представь себе: у тебя есть какой-нибудь Base — ну, типа, общая штука, а от него уже наследуется Derived, который внутри себя массив на сотню интов через new держит. Овердохуища памяти, да?
И вот ты, такой умный, создаёшь объект Derived, но хранишь указатель на него в переменной типа Base*. Потом, когда дело доходит до delete ptr;, начинается самое интересное. Если деструктор в Base — обычный, не виртуальный, то компилятор, видя тип указателя, тупо вызовет только деструктор Base. А деструктор Derived? Да похуй, он про него и не вспомнит! И что получается? Массив-то наш, выделенный в конструкторе Derived, так и остаётся висеть в памяти, никому не нужный. Утечка, ёпта! Сам от себя охуел бы, когда бы это в реальном проекте нашёл.
А теперь смотри, как надо. Объявляешь деструктор в Base как virtual ~Base(). Всё. Теперь, когда ты делаешь delete по указателю на базовый класс, компилятор понимает: «Ага, тут полиморфизм, надо проверить, что за объект на самом деле». Он лезет в таблицу виртуальных функций, видит: «Опа, а это же Derived!» И вызывает цепочку правильно: сначала ~Derived() (который массив наш освободит), потом уже ~Base(). Порядок, как в армии: от младшего к старшему.
Вот смотри на код, тут всё наглядно:
#include <iostream>
class Base {
public:
virtual ~Base() { // Вот эта магия — виртуальный деструктор!
std::cout << "Base destructor called.n";
}
};
class Derived : public Base {
int* dynamicArray;
public:
Derived() : dynamicArray(new int[100]) {}
~Derived() override {
delete[] dynamicArray; // Важный ресурс освобождаем тут
std::cout << "Derived destructor called.n";
}
};
int main() {
Base* ptr = new Derived(); // Указатель базового типа, но объект — производный
delete ptr; // И тут, благодаря virtual, вызовется ВСЁ, что надо
return 0;
}
Запустишь — вывод будет чёткий:
Derived destructor called.
Base destructor called.
Вот и весь секрет. Волнение ебать пропадает сразу, когда знаешь это правило. Если пишешь класс, от которого будут наследоваться другие, и будет работа через указатели на базовый тип — делай деструктор виртуальным. Иначе потом будешь искать, куда у тебя память девается, и терпения ноль ебать останется очень быстро. Это не просто рекомендация, а прям жёсткая необходимость, если не хочешь пиздопроебибны с утечками и неопределённым поведением. Запомни раз и навсегда: полиморфный базовый класс → виртуальный деструктор. Хуй с горы, иначе.