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

В каком порядке будут вызваны деструкторы при удалении указателя базового класса с виртуальным деструктором, указывающего на объект производного класса

2.0 Middle🔥 161 комментариев
#ООП и проектирование#Язык C++

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

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

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

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

Отличный вопрос про полиморфизм и управление памятью в C++. Давайте разберёмся с порядком деструкторов.

Краткий ответ

При удалении указателя базового класса, который указывает на объект производного класса:

  1. Первым вызывается деструктор производного класса
  2. Затем вызывается деструктор базового класса

Вызовы идут от наиболее производного класса к наиболее базовому (в обратном порядке наследования).

Практический пример

#include <iostream>
using namespace std;

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

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

int main() {
    Base* ptr = new Derived();
    delete ptr;  // Какой деструктор вызовется?
    return 0;
}

Вывод:

Derived destructor
Base destructor

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

С виртуальным деструктором (✓ правильно):

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

Вызываются оба деструктора благодаря virtual table (vtable).

Без виртуального деструктора (✗ проблема):

class Base {
public:
    ~Base() { cout << "Base cleanup\n"; }  // НЕ virtual!
};

Вызовется только деструктор Base, и память производного класса может утечь.

Многоуровневое наследование

Если цепочка наследования длинная:

class A { 
public: virtual ~A() { cout << "A\n"; } 
};

class B : public A { 
public: ~B() { cout << "B\n"; } 
};

class C : public B { 
public: ~C() { cout << "C\n"; } 
};

int main() {
    A* ptr = new C();
    delete ptr;  // Порядок?
    return 0;
}

Вывод:

C destructor
B destructor
A destructor

Порядок строго соответствует иерархии: от самого производного к самому базовому.

Почему так происходит

Компилятор генерирует код деструктора следующим образом:

  1. Вызывает пользовательский код деструктора текущего класса
  2. Автоматически вызывает деструкторы членов класса (в обратном порядке их объявления)
  3. Затем вызывает деструктор базового класса (неявно, как неявный вызов)
  4. Деструктор базового класса повторяет шаги 1-3 рекурсивно

Best Practices

✓ Правила:

  • Всегда делай виртуальным деструктор базового класса, если класс имеет производные
  • Используй virtual ~Base() = default; для явности
  • При использовании delete с базовым указателем убедись, что деструктор виртуальный
  • Если виртуальный деструктор не нужен — класс не должен быть базовым для полиморфизма

Пример правильного дизайна:

class Shape {
public:
    virtual ~Shape() = default;  // Явно виртуальный
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    ~Circle() override = default;  // override для ясности
    void draw() override { }
};

Почему это важно

Это фундаментальный паттерн в C++. Без правильного понимания деструкторов возникают:

  • Memory leaks — неочищенная память производного класса
  • Undefined behavior — вызов деструктора на частично инициализированном объекте
  • Трудноуловимые баги — проблемы проявляются только в специфических ситуациях

Поэтому эксперты всегда требуют virtual деструктор в полиморфных иерархиях — это вопрос безопасности и правильного управления ресурсами.

В каком порядке будут вызваны деструкторы при удалении указателя базового класса с виртуальным деструктором, указывающего на объект производного класса | PrepBro