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

Ответ

Порядок вызова конструкторов в C++ жёстко фиксирован и происходит снизу вверх — от базовых частей к производным:

  1. Конструкторы базовых классов. Вызываются в порядке их перечисления в списке наследования (слева направо).
  2. Конструкторы членов класса (полей). Вызываются в порядке их объявления в определении класса, независимо от порядка в списке инициализации конструктора.
  3. Тело конструктора самого класса.

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

Пример с множественным наследованием и полями:

#include <iostream>
#include <string>

class BaseA {
public:
    BaseA() { std::cout << "BaseA constructor.n"; }
};

class BaseB {
public:
    BaseB() { std::cout << "BaseB constructor.n"; }
};

class MemberX {
public:
    MemberX() { std::cout << "MemberX constructor.n"; }
};

class MemberY {
public:
    MemberY() { std::cout << "MemberY constructor.n"; }
};

// Наследование: BaseA, затем BaseB
// Объявление полей: MemberX mX, затем MemberY mY
class Derived : public BaseA, public BaseB {
    MemberX mX;
    MemberY mY;
public:
    // Список инициализации не влияет на порядок вызова конструкторов
    Derived() : mY(), mX() { // Порядок здесь роли не играет
        std::cout << "Derived constructor body.n";
    }
};

int main() {
    Derived d;
    return 0;
}

Вывод программы (порядок вызова):

BaseA constructor.    // 1. Базовый класс (первый в списке наследования)
BaseB constructor.    // 2. Базовый класс (второй в списке наследования)
MemberX constructor.  // 3. Поле (первое в объявлении класса)
MemberY constructor.  // 4. Поле (второе в объявлении класса)
Derived constructor body. // 5. Тело конструктора Derived

Ответ 18+ 🔞

Э, бля, слушай сюда, про конструкторы в плюсах. Тут, понимаешь, порядок вызова — это не просто так, наобум. Он жёстко зафиксирован, как будто тебе в армии распорядок дня выдали, и никаких отступлений. Идёт он, ёпта, снизу вверх — то есть от самых базовых штук к тем, что на них надстроены. Представь, что ты дом строишь: сначала фундамент, потом стены, потом крышу. Так и тут.

Вот смотри, как это работает, по шагам:

  1. Конструкторы базовых классов. Берутся по очереди, как они в списке наследования записаны. Слева направо, без вариантов.
  2. Конструкторы членов класса (полей). А вот это, бля, хитрая жопа! Вызываются они строго в том порядке, в каком ты их в классе объявил. И похуй, в каком порядке ты их в списке инициализации конструктора написал! Ты хоть задом наперёд их там перечисли — порядок вызова не изменится. Вот такая мудя.
  3. Тело конструктора самого класса. И только в самом конце, когда всё уже собрано и готово, выполняется код внутри фигурных скобок твоего конструктора.

А деструкторы, ясное дело, работают ровно наоборот — вызываются в строго обратном порядке. Разобрали крышу, потом стены, потом фундамент. Логично же.

Вот тебе пример, чтобы вообще всё стало понятно, как будто в рот мне чих-пых:

#include <iostream>
#include <string>

class BaseA {
public:
    BaseA() { std::cout << "BaseA constructor.n"; }
};

class BaseB {
public:
    BaseB() { std::cout << "BaseB constructor.n"; }
};

class MemberX {
public:
    MemberX() { std::cout << "MemberX constructor.n"; }
};

class MemberY {
public:
    MemberY() { std::cout << "MemberY constructor.n"; }
};

// Наследование: BaseA, затем BaseB
// Объявление полей: MemberX mX, затем MemberY mY
class Derived : public BaseA, public BaseB {
    MemberX mX;
    MemberY mY;
public:
    // Список инициализации не влияет на порядок вызова конструкторов
    Derived() : mY(), mX() { // Порядок здесь роли не играет
        std::cout << "Derived constructor body.n";
    }
};

int main() {
    Derived d;
    return 0;
}

И что же эта программа наваяет? Вот вывод, смотри:

BaseA constructor.    // 1. Базовый класс (первый в списке наследования)
BaseB constructor.    // 2. Базовый класс (второй в списке наследования)
MemberX constructor.  // 3. Поле (первое в объявлении класса, хоть в списке инициализации оно второе!)
MemberY constructor.  // 4. Поле (второе в объявлении класса, хоть в списке инициализации оно первое!)
Derived constructor body. // 5. И только теперь — тело конструктора Derived

Вот, понимаешь? Компилятору доверия ебать ноль в этом плане — он делает всё по своим правилам, а не так, как тебе в голову взбредёт. Запомни этот порядок, а то потом будешь охуевать, откуда ноги у багов растут.