Порядок вызова конструкторов / деструкторов в наследство

Небольшой вопрос о создании объектов. Скажем, у меня есть эти два класса:

struct A{
    A(){cout << "A() C-tor" << endl;}
    ~A(){cout << "~A() D-tor" << endl;}
};

struct B : public A{
    B(){cout << "B() C-tor" << endl;}
    ~B(){cout << "~B() D-tor" << endl;}

    A a;
};

и в основном я создаю экземпляр B:

int main(){
    B b;
}

Обратите внимание, что B происходит от A а также имеет поле типа A ,

Я пытаюсь выяснить правила. Я знаю, что при создании объекта сначала вызывается его родительский конструктор, и наоборот при разрушении.

Как насчет полей (A a; в этом случае)? когда B создан, когда он позвонит A конструктор? Я не определил список инициализации, есть какой-то список по умолчанию? А если нет списка по умолчанию? И тот же вопрос о разрушении.

6 ответов

Решение
  • Строительство всегда начинается с основания class, Если есть несколько баз classВ таком случае строительство начинается с самой левой базы. (примечание стороны: если есть virtual наследство то ему отдается больше предпочтений).
  • Затем поля-члены строятся. Они инициализируются в порядке их объявления
  • Наконец, class сам построен
  • Порядок деструктора в точности обратный

Независимо от списка инициализаторов порядок вызовов будет таким:

  1. База class Aконструктор
  2. class Bполе с именем a (типа class A) будет построен
  3. Производный class Bконструктор

Если предположить, что нет виртуального / множественного наследования (что несколько усложняет ситуацию), то правила просты:

  1. Память объекта выделяется
  2. Конструктор базовых классов выполняется, заканчивая большинством производных
  3. Инициализация члена выполнена
  4. Объект становится истинным экземпляром своего класса
  5. Код конструктора выполнен

Важно помнить, что до шага 4 объект еще не является экземпляром своего класса, поскольку он получает этот заголовок только после начала выполнения конструктора. Это означает, что если во время конструктора члена выдается исключение, деструктор объекта не выполняется, а только уже созданные части (например, члены или базовые классы) будут уничтожены. Это также означает, что если в конструкторе члена или базового класса вы вызываете любую виртуальную функцию-член объекта, то вызываемая реализация будет базовой, а не производной. Еще одна важная вещь, которую нужно помнить, это то, что член, перечисленный в списке инициализации, будет построен в том порядке, в котором они объявлены в классе, а НЕ в том порядке, в котором они появляются в списке инициализации (к счастью, большинство приличных компиляторов выдают предупреждение, если вы перечисляете члены в другом порядке от объявления класса).

Обратите внимание, что даже если во время выполнения кода конструктора this объект уже получил свой последний класс (например, в отношении виртуальной диспетчеризации), деструктор класса НЕ будет вызываться, пока конструктор не завершит свое выполнение. Только когда конструктор завершает выполнение, экземпляр объекта является настоящим гражданином первого класса среди экземпляров... до этого момента это всего лишь "желающий быть экземпляром" (несмотря на наличие правильного класса).

Разрушение происходит в точном обратном порядке: сначала выполняется деструктор объекта, затем он теряет свой класс (т.е. с этого момента объект считается базовым объектом), затем все члены уничтожаются в обратном порядке объявления и, наконец, процесс уничтожения базового класса выполняется до самого абстрактного родителя. Что касается конструктора, если вы вызываете любую виртуальную функцию-член объекта (прямо или косвенно) в базовом или деструкторе-члене, то выполненная реализация будет родительской, потому что объект потерял свой заголовок класса, когда деструктор класса завершил работу.

Базовые классы всегда создаются перед элементами данных. Элементы данных создаются в том порядке, в котором они объявлены в классе. Этот порядок не имеет ничего общего со списком инициализации. Когда элемент данных инициализируется, он просматривает ваш список инициализации для параметров и вызывает конструктор по умолчанию, если совпадений нет. Деструкторы для элементов данных всегда вызываются в обратном порядке.

Конструктор базового класса всегда выполняется first.so, когда вы пишете оператор B b; конструктор A сначала вызывается, а затем B class constructor.therefore выходные данные конструкторов будут в следующей последовательности:

A() C-tor
A() C-tor
B() C-tor
#include<iostream>

class A
{
  public:
    A(int n=2): m_i(n)
    {
    //   std::cout<<"Base Constructed with m_i "<<m_i<<std::endl;
    }
    ~A()
    {
    // std::cout<<"Base Destructed with m_i"<<m_i<<std::endl; 
     std::cout<<m_i;
    }

  protected:
   int m_i;
};

class B: public A
{
  public:
   B(int n ): m_a1(m_i  + 1), m_a2(n)
   {
     //std::cout<<"Derived Constructed with m_i "<<m_i<<std::endl;
   }

   ~B()
   {
   //  std::cout<<"Derived Destructed with m_i"<<m_i<<std::endl; 
     std::cout<<m_i;//2
     --m_i;
   }

  private:
   A m_a1;//3
   A m_a2;//5
};

int main()
{
  { B b(5);}
  std::cout <<std::endl;
  return 0;
}

Ответ в этом случае - 2531. Как конструктор вызывается здесь:

  1. Конструктор B::A(int n=2) называется
  2. B::B(5) конструктор называется
  3. B.m_A1::A(3) называется
  4. B.m_A2::A(5) называется

Деструктор так же называется:

  1. B:: ~ B () называется. т.е. m_i = 2, что уменьшает m_i до 1 в A.
  2. B.m_A2:: ~ A () называется. m_i = 5
  3. B.m_A1:: ~ A () называется. m_i = 3 4 B::~A() называется., m_i = 1

В этом примере построение m_A1 и m_A2 не имеет отношения к порядку порядка списка инициализации, но к порядку их объявления.

Выход из измененного кода:

A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor
Другие вопросы по тегам