Порядок вызова конструкторов / деструкторов по наследству - PullRequest
38 голосов
/ 24 сентября 2011

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

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 ]

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

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

  1. Конструктор Base class A
  2. class B Поле с именем a (типа class A) будет построен
  3. Производный class B конструктор
22 голосов
/ 24 сентября 2011

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

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

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

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

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

7 голосов
/ 24 сентября 2011

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

3 голосов
/ 24 сентября 2011

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

A() C-tor
A() C-tor
B() C-tor
1 голос
/ 09 ноября 2014
#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 не имеет отношения к порядку порядка списка инициализации, но к порядку их объявления.

0 голосов
/ 24 сентября 2011

Выход из модифицированного кода:

A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor
...