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

时间:2012-12-21 10:55:20

标签: c++

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

我不明白为什么“Destructor C!”将被调用。以及相关的析构函数的调用顺序。我觉得它似乎是堆栈推/弹。

此外: 为什么之前称之为“ D x(5); ”,但后面会给出相应的结果?

8 个答案:

答案 0 :(得分:3)

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

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

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

  • 必须在派生类之前构造基类。
  • 派生类必须在基类之前销毁。

答案 1 :(得分:3)

析构函数调用序列总是从派生到基础,就像弹出堆栈一样。这允许派生类清理基类分配的资源。在这种情况下,编译器知道如何构造此序列,因为它静态地知道对象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

运行此功能不会为~D调用x;对于y,两个析构函数都被调用。

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

答案 2 :(得分:2)

 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?

当控制流离开其定义范围时,自动对象(通常称为“局部变量”)按其定义的相反顺序被破坏。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

当你继承时,你“扩展”你继承的对象。因此,如果您要构建D,则需要C。当你需要销毁D时,你会破坏C,你可以扩展它。

答案 6 :(得分:0)

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

  

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

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

  

从范围退出(无论多么已完成)时,在该范围内构建的具有自动存储持续时间(3.7.3)的对象将按其构造的相反顺序销毁。

答案 7 :(得分:0)

  

进一步:为什么称它为“D x(5);”较早但相应的结果   稍后给出?

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

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

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