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

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

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

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

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

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

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

Порядок вызова деструкторов в C++ строго определён и критичен для правильного управления ресурсами. Он полностью противоположен порядку вызова конструкторов.

Основное правило: LIFO (Last In, First Out)

Деструкторы вызываются в ОБРАТНОМ порядке инициализации:

  • Последний инициализированный объект — первым разрушается
  • Это гарантирует, что зависимости между объектами сохраняются

1. Локальные переменные (автоматическое хранилище)

Деструкторы вызываются в обратном порядке объявления при выходе из области видимости:

#include <iostream>
using namespace std;

class Object {
public:
    string name;
    Object(const string& n) : name(n) { cout << "Ctor: " << name << endl; }
    ~Object() { cout << "Dtor: " << name << endl; }
};

int main() {
    cout << "--- Начало блока ---" << endl;
    {
        Object a("A");  // Создание
        Object b("B");  // Создание
        Object c("C");  // Создание
        cout << "--- Конец области видимости ---" << endl;
    }  // Деструкторы вызываются в порядке: C, B, A
    cout << "--- После блока ---" << endl;
    return 0;
}

/* Вывод:
--- Начало блока ---
Ctor: A
Ctor: B
Ctor: C
--- Конец области видимости ---
Dtor: C
Dtor: B
Dtor: A
--- После блока ---
*/

2. Объекты — члены класса

Деструкторы членов класса вызываются в обратном порядке их объявления в определении класса (не в порядке инициализации в конструкторе!):

class Container {
private:
    Object member_a{"A"};   // Объявлено первым
    Object member_b{"B"};   // Объявлено вторым
    Object member_c{"C"};   // Объявлено третьим
    
public:
    Container() {
        cout << "Container ctor" << endl;
    }
    
    ~Container() {
        cout << "Container dtor" << endl;
        // Деструкторы членов: C, B, A (в обратном порядке объявления)
    }
};

int main() {
    Container c;
    return 0;
}

/* Вывод:
Ctor: A
Ctor: B
Ctor: C
Container ctor
Container dtor
Dtor: C
Dtor: B
Dtor: A
*/

3. Порядок в инициализаторе членов (очень важно!)

Порядок в инициализаторе члена НЕ влияет на порядок деструкторов! Порядок определяется только порядком объявления в классе:

class Tricky {
private:
    Object c{"C"};  // Объявлено последним
    Object b{"B"};  // Объявлено вторым
    Object a{"A"};  // Объявлено первым
    
public:
    // ❌ НЕПРАВИЛЬНО: порядок в инициализаторе отличается от объявления
    Tricky() : c("C"), b("B"), a("A") {
        // Несмотря на этот порядок в инициализаторе,
        // члены инициализируются: a, b, c (порядок объявления)
        // Деструкторы вызовутся: c, b, a (обратный порядок объявления)
    }
};

/* Инициализация: A, B, C (как объявлены)
   Уничтожение:  C, B, A (обратно)
*/

Правило: При написании инициализатора члена, порядок должен соответствовать порядку объявления в классе:

// ✅ ПРАВИЛЬНО: порядок инициализатора = порядок объявления
class Good {
private:
    Object a{"A"};
    Object b{"B"};
    Object c{"C"};
    
public:
    Good() : a("A"), b("B"), c("C") {}  // Правильный порядок
};

4. Наследование

При наследовании деструкторы вызываются в следующем порядке:

  1. Деструктор самого класса
  2. Деструкторы членов (в обратном порядке объявления)
  3. Деструктор базового класса
class Base {
private:
    Object base_member{"BaseMember"};
    
public:
    virtual ~Base() { cout << "Base dtor" << endl; }
};

class Derived : public Base {
private:
    Object derived_member{"DerivedMember"};
    
public:
    ~Derived() override { cout << "Derived dtor" << endl; }
};

int main() {
    unique_ptr<Base> obj = make_unique<Derived>();
    return 0;  // При уничтожении unique_ptr:
}

/* Вывод:
Base constructor, DerivedMember
Derived dtor
Dtor: DerivedMember
Base dtor
Dtor: BaseMember
*/

5. Массивы объектов

Деструкторы вызываются для каждого элемента в обратном порядке:

int main() {
    {
        Object arr[3] = {Object("0"), Object("1"), Object("2")};
    }  // Деструкторы: [2], [1], [0]
    return 0;
}

/* Вывод:
Ctor: 0
Ctor: 1
Ctor: 2
Dtor: 2
Dtor: 1
Dtor: 0
*/

6. Динамическое выделение памяти

При использовании new[] деструкторы вызываются в обратном порядке при delete[]:

int main() {
    Object* arr = new Object[3];  // [0], [1], [2]
    delete[] arr;                  // [2], [1], [0]
    return 0;
}

7. Исключения и раскручивание стека

При выбросе исключения деструкторы локальных переменных вызываются при раскручивании стека (stack unwinding) в обратном порядке:

void level3() {
    Object obj3("obj3");
    throw runtime_error("Error in level3");
}

void level2() {
    Object obj2("obj2");
    level3();
}

void level1() {
    Object obj1("obj1");
    level2();
}

int main() {
    try {
        Object main_obj("main");
        level1();
    } catch (const exception& e) {
        cout << "Caught: " << e.what() << endl;
    }
    return 0;
}

/* Вывод:
Ctor: main
Ctor: obj1
Ctor: obj2
Ctor: obj3
Dtor: obj3           // Раскручивание стека
Dtor: obj2
Dtor: obj1
Caught: Error in level3
Dtor: main
*/

8. Глобальные объекты

Деструкторы глобальных объектов вызываются в обратном порядке их инициализации (в конце программы):

class GlobalObject {
public:
    string name;
    GlobalObject(const string& n) : name(n) { cout << "Global ctor: " << name << endl; }
    ~GlobalObject() { cout << "Global dtor: " << name << endl; }
};

GlobalObject g1("G1");  // Инициализируется первым
GlobalObject g2("G2");  // Инициализируется вторым

int main() { return 0; }

/* Вывод:
Global ctor: G1
Global ctor: G2
Global dtor: G2      // В обратном порядке
Global dtor: G1
*/

Практический пример: сложная иерархия

class Resource {
protected:
    string name;
public:
    Resource(const string& n) : name(n) { cout << "Resource: " << name << endl; }
    virtual ~Resource() { cout << "~Resource: " << name << endl; }
};

class FileHandler : public Resource {
private:
    Object lock{"FileLock"};
    Object buffer{"Buffer"};
public:
    FileHandler() : Resource("File") {}
    ~FileHandler() {
        cout << "~FileHandler" << endl;
        // Порядок деструкторов: Buffer, FileLock, ~Resource
    }
};

int main() {
    {
        FileHandler fh;
    }
    return 0;
}

/* Вывод:
Resource: File
Ctor: FileLock
Ctor: Buffer
~FileHandler
Dtor: Buffer
Dtor: FileLock
~Resource: File
*/

Чеклист правильного управления деструкторами

  1. Помните о LIFO — последний созданный, первый разрушен
  2. Порядок объявления членов — определяет порядок деструкторов
  3. Инициализатор членов — должен соответствовать порядку объявления
  4. Виртуальные деструкторы — обязательны при наследовании
  5. noexcept деструкторы — никогда не выбрасывайте исключения
  6. RAII принцип — использование деструкторов для очистки

Вывод: Понимание порядка вызова деструкторов критично для управления ресурсами, предотвращения утечек и написания безопасного C++ кода. Основное правило: LIFO и порядок объявления в классе определяют всё.

В каком порядке вызываются деструкторы | PrepBro