请解释析构函数调用的顺序

Please explain the sequence of destructor invocations

本文关键字:顺序 函数调用 析构 解释      更新时间:2023-10-16
using namespace std;
class C
{
    int a;
    public:
        C(int aa=0) {a=aa;}
        ~C() {cout << "Destructor C!" << a << endl;}
};
class D: public C
{
    int b;
    public:
        D(int aa=0, int bb=0): C(aa) {b=bb;}
        ~D() {cout << "Destructor D!" << b << endl;}
};
void test()
{
    D x(5);
    D y(6,7);
}
int main()
{
    test();
}

以上是代码,下面是运行结果:

Destructor D!7
Destructor C!6
Destructor D!0
Destructor C!5

我不明白为什么会调用"析构函数C!"。以及相关析构函数的调用序列。我觉得它看起来像堆栈推送/弹出。

进一步:为什么它更早地调用">D x(5(;",但稍后给出相应的结果?

构函数调用序列总是从派生序列转到基基,就像弹出堆栈一样。这允许派生类清理基类分配的资源。在这种情况下,编译器知道如何构造此序列,因为它知道静态xy对象的确切类型。

但是,在某些情况下,此序列会被打破。请考虑对代码进行以下修改:

void test()
{
    C *x = new D(5);
    D *y = new D(6,7);
    delete x;
    delete y;
}

它产生以下输出:

Destructor C!5
Destructor D!7
Destructor C!6

运行此操作不会对x产生~D调用;对于y,两个析构函数都被调用。

这是因为您没有在基类中声明析构函数virtual。当析构函数不是虚拟的时,编译器不知道在对象由指向基类的指针引用的情况下,它必须调用派生类的析构函数。这就是为什么您应该始终在必须继承的类中使析构函数成为虚拟并动态分配资源的原因。

派生类的构造函数和析构函数调用序列如下所示:

Base  Class  Constructor
Derived  Class  Constructor
Derived  Class  Destructor
Base  Class  Destructor

由于派生类建立在基类之上:

  • 基类必须在派生类之前构造。
  • 派生类必须在基类之前销毁。
 I feel that it seems like the stack push/pop.

D类派生自C,当调用D构造函数时,C构造函数将首先调用,析构函数则以相反的方式调用。

Further: Why it calls "D x(5);" earlier but the corresponding result is given later?

当控制流离开其定义范围时,自动对象(通常称为"局部变量"(将按其定义的相反顺序销毁。

清理对象时,首先调用派生类的析构函数,然后调用基类的析构函数。

创建D实例时,将调用C的构造函数,因为D继承自它。
当您再次销毁D时,它将调用D析构函数,然后调用C析构函数。

当你继承时,你"扩展"你继承的对象。所以如果你想建立D,你需要C.当你需要摧毁D时,你会摧毁C,你建造它是为了能够扩展它。

>调用 C 的析构函数是因为销毁派生类的对象包括销毁其基类子对象。根据[class.dtor]第8段:

执行析构函数的主体后...X 类调用的析构函数...X 直接基类的析构函数...

当控制流退出作用域(例如函数test()结尾(时,本地对象将以LIFO方式销毁:首先创建的对象最后销毁。根据[stmt.jump]第2段:

退出作用域时(无论如何完成(,在该作用域中构造的具有自动存储持续时间 (3.7.3( 的对象将按其构造的相反顺序销毁。

进一步:为什么它更早地调用"D x(5(;",但相应的结果 以后给的?

这些对象在堆栈上分配(而D * d1 = new D();将在上分配(。

不是对D x(5)的调用提供输出,而是对析构函数的调用~D实例超出范围时发生,在这种情况下,在退出main()时。

因为它是堆栈内存,所以解除分配的顺序与分配相反;