58

在 C++ 中调用析构函数和构造函数的顺序是什么?使用一些基类和派生类的例子

4

5 回答 5

74

顺序是:

  1. 基础构造函数
  2. 派生构造函数
  3. 派生的析构函数
  4. 基础析构函数

例子:

class B
{
public:
  B()
  {  
    cout<<"Construct B"<<endl;
  }

  virtual ~B()
  {
    cout<<"Destruct B"<<endl;
  }
};

class D : public B
{
public:
  D()
  {  
    cout<<"Construct D"<<endl;
  }

  virtual ~D()
  {
    cout<<"Destruct D"<<endl;
  }
};



int main(int argc, char **argv)
{
  D d; 
  return 0;
}

示例输出:

构造 B

构造 D

破坏D

破坏B

多级继承就像一个堆栈:

如果您考虑将一个项目推入堆栈作为构造,并将其取出作为破坏,那么您可以将多个级别的继承视为堆栈。

这适用于任意数量的级别。

示例 D2 派生自 D 派生自 B。

将 B 压入堆栈,将 D 压入堆栈,将 D2 压入堆栈。所以施工顺序是B、D、D2。然后找出破坏顺序开始弹出。D2、D、B

更复杂的例子:

更复杂的例子请看@JaredPar 提供的链接

于 2009-03-17T14:13:23.930 回答
22

C++ FAQ Lite 中提供了这些事件的详细描述,包括虚拟继承和多重继承。第 25.14 和 25.15 节

https://isocpp.org/wiki/faq/multiple-inheritance#mi-vi-ctor-order

于 2009-03-17T14:16:04.077 回答
10

另外,请记住,虽然数组元素是先构造的 -> 最后一个,但它们会以相反的顺序被破坏:最后一个 -> 第一。

于 2009-03-17T14:33:58.090 回答
7

我必须添加到以前的答案,因为每个人似乎都忽略了它

当您创建派生类实例时,确实会在派生的构造函数的代码之前调用类的构造函数的代码,但请记住,从技术上讲派生基地

当你调用派生类析构函数时,派生析构函数内的代码确实在基析构函数的代码之前被调用,但也要记住,类在派生之前销毁

当我说created/destroyed时,我实际上指的是allocated/deallocated

如果您查看这些实例的内存布局,您会发现派生实例构成了基础实例。例如:

派生的内存:0x00001110 到 0x00001120

基础内存:0x00001114 到 0x00001118

因此,派生类必须在构造中的基类之前分配。派生类必须在销毁中的基类之后释放。

如果您有以下代码:

class Base 
{
public:
    Base()
    {
        std::cout << "\n  Base created";
    }
    virtual ~Base()
    {
        std::cout << "\n  Base destroyed";
    }
}

class Derived : public Base 
{
public:
    Derived()
    // Derived is allocated here 
    // then Base constructor is called to allocate base and prepare it
    {
        std::cout << "\n  Derived created";
    }
    ~Derived()
    {
        std::cout << "\n  Derived destroyed";
    }   
    // Base destructor is called here
    // then Derived is deallocated
}

因此,如果您创建Derived d;并让它超出范围,那么您将在@Brian 的答案中获得输出。但是内存中的对象行为并不是真正的顺序相同,更像是这样:

建造:

  1. 派生分配

  2. 基地分配

  3. 基础构造函数调用

  4. 派生构造函数调用

破坏:

  1. 派生的析构函数调用

  2. 基析构函数调用

  3. 基数解除分配

  4. 派生解除分配

于 2017-03-07T19:06:47.477 回答
2

这在order-dtors-for-members中有明确描述。基本上,规则是“先构建,最后销毁”。

构造函数调用顺序:

  1. base 的构造函数在“:”之后按出现的顺序调用
  2. 派生类成员的构造函数按出现顺序在类的构造函数之前调用

析构函数的调用顺序与被调用的构造函数相反。

例子:

#include <iostream>

struct base0 {  base0(){printf("%s\n", __func__);};~base0(){printf("%s\n", __func__);}; };
struct base1 { base1(){printf("%s\n", __func__);}; ~base1(){printf("%s\n", __func__);};};
struct member0 { member0(){printf("%s\n", __func__);};  ~member0(){printf("%s\n", __func__);};};
struct member1 { member1(){printf("%s\n", __func__);}; ~member1(){printf("%s\n", __func__);};};
struct local0 { local0(){printf("%s\n", __func__);}; ~local0(){printf("%s\n", __func__);}; };
struct local1 { local1(){printf("%s\n", __func__);};  ~local1(){printf("%s\n", __func__);};};
struct derived: base0, base1
{
  member0 m0_;
  member1 m1_;
  derived()
  {
    printf("%s\n", __func__);
    local0 l0;
    local1 l1;
  }
  ~derived(){printf("%s\n", __func__);};
};
int main()
{
  derived d;
}

输出:

base0
base1
member0
member1
derived
local0
local1
~local1
~local0
~derived
~member1
~member0
~base1
~base0
于 2019-10-03T13:18:30.073 回答