В каком порядке вызываются деструкторы в C++?

Ответ

В C++ деструкторы вызываются в строго определённом порядке, обратном порядку конструирования:

  1. Для одного объекта: Тело деструктора самого класса выполняется первым.
  2. Для членов класса (полей): Деструкторы полей вызываются после тела деструктора класса, в порядке, обратном их объявлению в определении класса.
  3. Для базовых классов: Деструкторы базовых классов вызываются после деструкторов всех полей, в порядке, обратном порядку наследования.
  4. Для объектов в стеке: При выходе из области видимости деструкторы локальных объектов вызываются в порядке LIFO (последний созданный — первый уничтоженный).

Пример, демонстрирующий порядок:

#include <iostream>
#include <string>

class Member1 {
public:
    ~Member1() { std::cout << "Member1 destroyed.n"; }
};

class Member2 {
public:
    ~Member2() { std::cout << "Member2 destroyed.n"; }
};

class Base {
public:
    ~Base() { std::cout << "Base destroyed.n"; }
};

class Derived : public Base {
    Member1 m1; // Объявлен первым
    Member2 m2; // Объявлен вторым
public:
    ~Derived() {
        std::cout << "Derived body.n"; // Тело деструктора
    }
};

int main() {
    Derived d;
    // При выходе из main() для объекта 'd' порядок будет:
    // 1. Тело ~Derived()
    // 2. ~Member2() (обратный порядок объявления полей)
    // 3. ~Member1()
    // 4. ~Base() (обратный порядок наследования)
    return 0;
}

Ожидаемый вывод:

Derived body.
Member2 destroyed.
Member1 destroyed.
Base destroyed.

Ответ 18+ 🔞

А, ну это классика, ёпта! Сижу тут, кофе попиваю, и вспомнил, как один мой знакомый чувак на ровном месте накрылся медным тазом из-за того, что порядок деструкторов не понимал. Ну, думаю, надо про это рассказать, а то народ потом охуевает, когда программа падает, а почему — хуй поймёшь.

В общем, в C++ эта штука работает чётко, как часы, но если не знать правил, то можно сам от себя охуеть. Деструкторы вызываются в строго определённом порядке, и это не просто так, а чтобы всё красиво разобрать, как конструктор собрал.

Вот смотри, как это работает, на пальцах:

  1. Для одного объекта: Сначала выполняется само тело деструктора класса. То есть твой код, который ты там написал в ~Derived(), отрабатывает самым первым. Это логично, ты же сначала свою внутреннюю кухню прибираешь, а потом уже помощников распускаешь.
  2. Для членов класса (полей): А вот потом, уже после твоего кода, начинают вызываться деструкторы полей-объектов. И вызываются они в обратном порядке относительно того, как ты их объявил в классе. Кто последний объявлен — тот первый и уничтожается. Почему? А хуй его знает, так исторически сложилось, но это железное правило.
  3. Для базовых классов: И только когда все поля разобраны, наступает черёд базовых классов. Их деструкторы вызываются тоже в обратном порядке наследования. Если ты наследуешься от Base, то ~Base() вызовется в самом конце.
  4. Для объектов в стеке: Ну и отдельная песня — локальные объекты в функции. Тут принцип простой, как мир: последний созданный — первый на выход. LIFO, как в стопке тарелок. Поставил сверху — сверху же и снял.

А теперь, чтобы не быть голословным, вот тебе живой пример, который всё расставит по полочкам. Смотри на этот код и не бзди.

#include <iostream>
#include <string>

class Member1 {
public:
    ~Member1() { std::cout << "Member1 destroyed.n"; }
};

class Member2 {
public:
    ~Member2() { std::cout << "Member2 destroyed.n"; }
};

class Base {
public:
    ~Base() { std::cout << "Base destroyed.n"; }
};

class Derived : public Base {
    Member1 m1; // Объявлен первым
    Member2 m2; // Объявлен вторым
public:
    ~Derived() {
        std::cout << "Derived body.n"; // Тело деструктора
    }
};

int main() {
    Derived d;
    // При выходе из main() для объекта 'd' порядок будет:
    // 1. Тело ~Derived()
    // 2. ~Member2() (обратный порядок объявления полей)
    // 3. ~Member1()
    // 4. ~Base() (обратный порядок наследования)
    return 0;
}

И что же мы увидим, когда программа закончится и объект d начнёт разбираться? А вот что, ядрёна вошь:

Derived body.
Member2 destroyed.
Member1 destroyed.
Base destroyed.

Вот и вся магия. Сначала своё тело, потом поля задом наперёд, а в самом конце — батя. Запомни эту последовательность, и волнение ебать по поводу утечек памяти или кривого разрушения ресурсов у тебя отпадёт. Главное — не выёбывайся и не пытайся вручную в деструкторе члены удалять, которые сами удаляться должны. А то будет тебе хиросима, а не программа.