← Назад к вопросам

Зачем нужен виртуальный деструктор?

1.0 Junior🔥 301 комментариев
#ООП и проектирование#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен виртуальный деструктор

Виртуальный деструктор — это критическое требование для полиморфных классов, обеспечивающее корректное удаление объектов через базовый указатель. Без него программа страдает от утечек памяти и корупции памяти.

Проблема: деструктор без virtual

Когда производный класс наследует базовый без virtual деструктора:

class Base {
public:
    ~Base() { std::cout << "Base destructor\n"; }
};

class Derived : public Base {
private:
    std::string buffer;  // Требует очистки
public:
    ~Derived() { 
        std::cout << "Derived destructor\n";
        // buffer автоматически очистится
    }
};

Base* ptr = new Derived();
delete ptr;  // ПРОБЛЕМА!

Что происходит:

  • Вызывается только ~Base()
  • Деструктор Derived() НЕ вызывается
  • Память buffer не освобождается
  • УТЕЧКА ПАМЯТИ

Решение: virtual деструктор

class Base {
public:
    virtual ~Base() { std::cout << "Base destructor\n"; }
};

class Derived : public Base {
private:
    std::string buffer;
public:
    ~Derived() override {  // override для ясности
        std::cout << "Derived destructor\n";
    }
};

Base* ptr = new Derived();
delete ptr;  // Правильно!
// Вывод:
// Derived destructor
// Base destructor

Как virtual деструктор работает

Виртуальные функции используют VTable (virtual table):

// VTable для Base
struct Base_VTable {
    void (*destructor)(Base*);
};

Base* ptr = new Derived();
delete ptr;  // Ищет в VTable указателя Derived
             // Вызывает Derived::~Derived()

Компилятор автоматически определяет реальный тип объекта в runtime и вызывает правильный деструктор.

Практические сценарии

Интерфейсы и абстрактные классы:

class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void log(const std::string& msg) = 0;
};

class FileLogger : public ILogger {
private:
    std::ofstream file;
public:
    ~FileLogger() override { file.close(); }
    void log(const std::string& msg) override { file << msg; }
};

Полиморфные контейнеры:

std::vector<std::unique_ptr<Base>> objects;
objects.push_back(std::make_unique<Derived1>());
objects.push_back(std::make_unique<Derived2>());

// При уничтожении вектора — каждый деструктор вызовется правильно

С shared_ptr:

std::shared_ptr<Base> ptr = std::make_shared<Derived>();
// При delete — правильно вызовется Derived::~Derived()

Правило: где virtual деструктор обязателен

Используй virtual деструктор если:

  • Класс предназначен для наследования
  • Есть виртуальные методы
  • Класс может быть удален через базовый указатель

Не нужен если:

  • Класс final (не наследуется)
  • Нет виртуальных методов
  • Никогда не удаляется через базовый указатель

Производительность

  • Оверхед: один указатель VTable на объект (8 байт на 64-bit)
  • При удалении: одна виртуальная диспетчеризация (очень быстро)
  • Оптимизирует: компилятор часто инлайнит при final классах
// GCC с -O2 может оптимизировать:
Derived* ptr = new Derived();
delete ptr;  // Виртуальный вызов опускается

Виртуальный деструктор — это не опциональный бонус, а обязательная практика для любого полиморфного класса в production коде.