42

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

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的构造函数?我还没有定义初始化列表,是否有某种默认列表?如果没有默认列表?还有关于破坏的同样问题。

4

6 回答 6

79
  • 建设总是从基地开始class。如果有多个 base class,则从最左边的 base 开始构建。(旁注:如果有virtual继承,则优先级更高)。
  • 然后构建成员字段。它们按照声明的顺序进行初始化
  • 最后,class自身被构建
  • 析构函数的顺序正好相反

不管初始化列表如何,调用顺序都是这样的:

  1. Baseclass A的构造函数
  2. class B的字段名为a(类型class A)将被构造
  3. Derivedclass B的构造函数
于 2011-09-24T13:28:28.337 回答
24

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

  1. 分配对象内存
  2. 基类的构造函数被执行,以大多数派生结束
  3. 执行成员初始化
  4. 对象成为其类的真实实例
  5. 构造函数代码被执行

需要记住的重要一点是,在第 4 步之前,该对象还不是其类的实例,因为它只有在构造函数开始执行后才获得此名称。这意味着如果在成员的构造函数期间抛出异常,则不会执行对象的析构函数,而只会销毁已构造的部分(例如成员或基类)。这也意味着,如果在成员或基类的构造函数中调用对象的任何虚拟成员函数,则调用的实现将是基类,而不是派生类。要记住的另一件重要的事情是,初始化列表中列出的成员将按照它们在类中声明的顺序构造,

还要注意,即使在构造函数代码的执行过程中,对象已经获得了它的最终类(例如,关于虚拟调度),除非构造函数完成其执行this,否则不会调用类的析构函数。只有当构造函数完成执行时,对象实例才是实例中真正的一等公民……在那之前只是一个“想要的实例”(尽管有正确的类)。

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

于 2011-09-24T13:50:08.920 回答
7

基类总是在数据成员之前构建。数据成员按照它们在类中声明的顺序构造。此顺序与初始化列表无关。当一个数据成员被初始化时,它会在你的初始化列表中查找参数,如果没有匹配则调用默认构造函数。数据成员的析构函数总是以相反的顺序调用。

于 2011-09-24T13:33:58.993 回答
3

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

A() C-tor
A() C-tor
B() C-tor
于 2011-09-24T14:41:03.280 回答
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) 被调用

调用相同方式的析构函数:

  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

在本例中,m_A1 & m_A2 的构造与初始化列表顺序无关,而与它们的声明顺序无关。

于 2014-11-09T19:08:05.723 回答
0

修改后的代码的输出是:

A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor
于 2011-09-24T13:50:27.230 回答