继承中调用构造函数/析构函数的顺序

时间:2011-09-24 13:20:34

标签: c++ constructor order-of-execution call-hierarchy

关于创建对象的一个​​小问题。说我有这两个类:

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;
};

在main中我创建了一个B

的实例
int main(){
    B b;
}

请注意,B来自A,并且还有A类型的字段。

我想弄明白这些规则。我知道在构造对象时首先调用它的父构造函数,反之则在破坏时调用它。

字段(本例中为A a;)怎么样?创建B时,它何时会调用A的构造函数?我还没有定义初始化列表,是否有某种默认列表?如果没有默认列表?关于破坏的同样问题。

6 个答案:

答案 0 :(得分:74)

  • 构建始终以基础class开头。如果有多个基础class es,则构造从最左边的基数开始。 (旁注:如果存在virtual继承,则会给予更高的偏好。)
  • 然后构建成员字段。它们在初始化 命令他们被宣布
  • 最后,构建class本身
  • 析构函数的顺序完全相反

无论初始化列表如何,呼叫顺序都是这样的:

  1. 基础class A的构造函数
  2. class B将构建名为a(类型为class A)的字段
  3. 派生class B的构造函数

答案 1 :(得分:22)

假设没有虚拟/多重继承(这会使事情变得复杂),那么规则很简单:

  1. 分配了对象内存
  2. 执行基类的构造函数,以大多数派生的
  3. 结束
  4. 执行成员初始化
  5. 该对象成为其类的真实实例
  6. 执行构造函数代码
  7. 要记住的一件重要事情是,直到第4步,对象还不是其类的实例,因为它只有在构造函数的执行开始后才能获得此标题。这意味着如果在成员的构造函数期间抛出异常,则不执行该对象的析构函数,但是仅销毁已构造的部分(例如成员或基类)。这也意味着如果在成员或基类的构造函数中调用对象的任何虚拟成员函数,则调用的实现将是基础实现,而不是派生实现。 另一个要记住的重要事项是初始化列表中列出的成员将按照它们在类中声明的顺序构建,而不是按照它们在初始化列表中出现的顺序构建(幸运的是,如果列出成员,大多数正常的编译器都会发出警告与班级宣言的顺序不同。)

    另请注意,即使在执行构造函数代码期间this对象已经获得其最终类(例如,关于虚拟分派),也不会调用类的析构函数,除非构造函数完成执行。只有当构造函数完成执行时,对象实例才是实例中真正的第一类公民...在此之前只是一个“想成为实例”(尽管有正确的类)。

    破坏以完全相反的顺序发生:首先执行对象析构函数,然后它失去它的类(即从对象上的这一点被视为基础对象)然后所有成员以反向声明顺序销毁,最后是基础类销毁过程执行到最抽象的父级。对于构造函数,如果在基类或成员析构函数中调用对象的任何虚拟成员函数(直接或间接),则执行的实现将是父实例,因为在类析构函数完成时对象丢失了其类标题。

答案 2 :(得分:7)

基类始终在数据成员之前构建。数据成员按照在类中声明的顺序构造。此顺序与初始化列表无关。初始化数据成员时,它将查看参数的初始化列表,如果没有匹配则调用默认构造函数。始终以相反的顺序调用数据成员的析构函数。

答案 3 :(得分:3)

基类构造函数总是先执行。因此,当您编写语句B b;时,首先调用A的构造函数,然后调用B类构造函数。因此构造函数的输出将为按以下顺序排列:

A() C-tor
A() C-tor
B() C-tor

答案 4 :(得分:1)

#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)被称为
  5. 调用相同的析构函数:

    1. B ::〜B()被调用。即m_i = 2,在A中将m_i减少为1。
    2. B.m_A2 :: ~A()被调用。 m_i = 5
    3. B.m_A1 :: ~A()被调用。 m_i = 3 4 B ::〜A()被称为。,m_i = 1
    4. 在这个例子中,构造m_A1&amp; m_A2与初始化列表顺序的顺序无关,但与它们的声明顺序无关。

答案 5 :(得分:0)

修改后的代码输出为:

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