C++构造函数和析构函数的顺序

11

我在尝试编写一个关于基类和成员的构造和析构的代码,但是一些构造函数和析构函数的顺序让我感到困惑, 这段代码的输出结果是:

Base1 constructor
Member1 constructor
Member2 constructor
Derived1 constructor
Member3 constructor
Member4 constructor
Derived2 constructor
Derived2 destructor
Member4 destructor
Member3 destructor
Derived1 destructor
Member2 destructor
Member1 destructor
Base1 destructor

看到前四行, 但是我觉得顺序应该是

Base1 constructor
Derived1 constructor
Member1 constructor
Member2 constructor

有人可以给我一些解释吗?

#include "stdafx.h"
#include <fstream>
using namespace std;
ofstream out("order.out");

#define CLASS(ID) class ID { \
public: \
  ID(int) { out << #ID " constructor\n"; } \
  ~ID() { out << #ID " destructor\n"; } \
};

CLASS(Base1);
CLASS(Member1);
CLASS(Member2);
CLASS(Member3);
CLASS(Member4);

class Derived1 : public Base1 {
  Member1 m1;
  Member2 m2;
public:
  Derived1(int) : m2(1), m1(2), Base1(3) {
    out << "Derived1 constructor\n";
  }
  ~Derived1() {
    out << "Derived1 destructor\n";
  }
};

class Derived2 : public Derived1 {
  Member3 m3;
  Member4 m4;
public:
  Derived2() : m3(1), Derived1(2), m4(3) {
    out << "Derived2 constructor\n";
  }
  ~Derived2() {
    out << "Derived2 destructor\n";
  }
};

int main() {
  Derived2 d2;
} ///:~

2
请提供一个完整的最小代码示例以重现问题。同时,+1。 - Matthieu M.
5个回答

10

构造函数在继承层次结构中是自上而下调用的:

- base class member objects
- base class constructor body
- derived class member objects
- derived class constructor body

输出是正确的。

我们来简化你的代码:

struct BaseMember
{
   BaseMember() { cout << "base member" <<endl; }
};
struct Base
{
   BaseMember b;
   Base() { cout << "base" << endl; }
};
struct DerivedMember
{
   DerivedMember() { cout << "derived member" << endl; }
};
struct Derived : public Base
{
   DerivedMember d;
   Derived() { cout << "derived" << endl; }
};

Derived d;

创建 d 时,它将首先创建 Base 部分。在进入构造函数体之前,所有成员对象都被初始化。所以 BaseMember 是第一个被初始化的对象。

接下来,进入 Base 的构造函数。

在进入 Derived 的构造函数之前,Derived 的成员对象被初始化,因此 DerivedMember 被创建,然后调用 Derived 构造函数。

这是因为当您进入派生类的构造函数体时,基类和成员对象必须完全初始化。

编辑 正如 Matthieu 指出的那样,成员对象初始化的顺序由它们在类定义中出现的顺序指定,而不是它们在初始化列表中出现的顺序。


注意1:对于虚基类,情况会有些奇怪。注意2:列出基类和属性的顺序并不重要,这完全取决于它们在类定义中出现的顺序。 - Matthieu M.
@MatthieuM. 你说的“listed”是什么意思? - Luchian Grigore
@MatthieuM。哦,你是指初始化列表吗?是的,没错。 - Luchian Grigore
@LuchianGrigore:啊,是的,初始化列表。对于初学者来说,认为它有任何影响是一个典型的错误。 - Matthieu M.
@user1279988 这位绅士值得一个答案勾选。迄今为止回答你问题的许多人也值得如此。 - user1309389

2

类的构造函数执行晚于其字段的构造函数。基类和它的成员的构造函数先于派生类及其成员的构造函数执行。


1

在初始化列表中完成的初始化发生在Derived1构造函数的主体之前,因此您首先看到m1和m2的输出。

更完整的答案是:首先构造基类子对象,然后按照它们在类中声明的顺序(而不是在初始化列表中的顺序)构造成员,最后执行构造函数体。销毁按相反的顺序进行。

在这种情况下,当您构造Derived2时,它首先会构造Derived1子对象。这又涉及首先构造Base子对象,因此它首先完成了这个步骤。然后它构造Derived1的成员,执行Derived1构造函数的主体,构造Derived2的成员,最后执行Derived2构造函数的主体。因此观察到了输出结果。


1
这是因为m1m2Derived1构造函数的初始化列表中被初始化。在构造函数的主体进入之前,初始化列表中的所有内容都会被构造。

1
考虑以下程序,它将使您的想法更加清晰!
#include<iostream.h>
class A
{
public:
  A()
    {
    cout<<"\nI am the base class constructor!";
    }
  ~A()
    {
    cout<<"\nI am the base class destructor!";
    }
};

class B : public A
{
public:
  B()
    {
    cout<<"\nI am the derived class constructor!";
    }
  ~B()
    {
    cout<<"\nI am the derived class destructor!";
    }
};

int main()
  {
  B obj;
  return 0;
  }

以上程序的输出结果如下:

我是基类构造函数!

我是派生类构造函数!

我是派生类析构函数!

我是基类析构函数!

我认为这解释和澄清了您关于构造函数和析构函数调用顺序的疑问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接