使用继承时构造函数/析构函数调用的顺序

Order of Constructor/Destructor Calls When Using Inheritance

本文关键字:析构 函数调用 顺序 构造函数 继承      更新时间:2023-10-16

我试图通过编写一些示例代码并尝试遵循程序流程来理解调用构造函数和析构函数的顺序。在大多数情况下,我能够理解(在需要时在谷歌的帮助下(。然而,在一个特殊的情况下,我遇到了一些障碍。

这是我正在使用的程序:

#include <iostream>
class baseC
{
public:
        baseC() { std::cout << "Calling constructor of base class: " << std::endl; }
        virtual char const * getName(){ return "Base Class";}
        ~baseC(){ std::cout << "Calling destructor of base class: " << std::endl;}
};
class childC : public baseC
{
public:
        childC() { std::cout << "Calling constructor of child class: " << std::endl; }
        char const * getName(){ return "Child Class";}
        ~childC(){ std::cout << "Calling destructor of child class: " << std::endl; }
};
int main()
{
        baseC c3 = childC();
        std::cout << c3.getName() << std::endl;
}

这是我得到的输出:

$ g++ test_vd_se.cpp -o test; ./test
Calling constructor of base class: 
Calling constructor of child class: 
Calling destructor of child class: 
Calling destructor of base class: 
Base Class
Calling destructor of base class:

编译器似乎首先创建一个基类和子类(这是意料之中的(,但是它继续销毁这两个类,但它可以从基类调用成员函数并继续再次销毁基类。

如果有人能解释为什么按这个顺序调用函数,我将不胜感激。

这里的问题是你正在切片对象。

baseC c3 = childC();

将创建一个临时childC,然后将该对象复制到 c3 中。 这就是为什么你看到

Calling constructor of base class:  // create base part of temporary
Calling constructor of child class: // create temporary
// the copy happens here but you do not output when copying
Calling destructor of child class:  // destroy base part of temporary
Calling destructor of base class:   // destroy temporary

执行此操作的正确方法是使用智能指针。 如果将main()更改为

int main()
{
        auto c3 = std::make_unique<childC>();
        std::cout << c3->getName() << std::endl;
}   

或者,如果您无法访问智能指针:

int main()
{
        baseC* c3 = new childC();
        std::cout << c3->getName() << std::endl;
        delete c3;
}

你会得到:

Calling constructor of base class: 
Calling constructor of child class: 
Child Class
Calling destructor of child class: 
Calling destructor of base class: 

现场示例

我们还需要制作~baseC() virtual以便调用正确的析构函数。

virtual ~baseC(){ std::cout << "Calling destructor of base class: " << std::endl;}

您还将注意到,现在打印Child Class而不是Base Class因为现在我们有一个指针动态调度启动并调用正确的虚函数。

"异常"来自以下赋值:

baseC c3 = childC();

首先创建一个临时childC,按顺序从上到下调用构造函数:

Calling constructor of base class: 
Calling constructor of child class:

然后进行赋值,从而创建一个baseC对象。但这一次,调用的不是构造函数,而是默认的复制构造函数这就是为什么我们没有再次观察Calling constructor of base class:(用于对象c3的构造(。为了证明这一点,请尝试将复制构造函数添加到 baseC 类中:

  baseC(const baseC& other) { std::cout << "Calling Copy-constructor of base class: " << std::endl; }

使用相同的主函数,您将在输出中观察该句子两次:

Calling constructor of base class: 
Calling constructor of child class:
**Calling copy-constructor of base class:**
Calling destructor of child class: 
Calling destructor of base class: 
Base Class
Calling destructor of base class:

最后,临时子对象被销毁,因此自下而上调用析构函数。

Calling destructor of child class: 
Calling destructor of base class: 

现在baseC对象c3仍然存在,调用了getName((方法,该方法输出:

Child Class

然后当变量c3超出范围(main() 结束(时,c3被销毁:

Calling destructor of base class:

最后,baseC& c3 = ChildC();的情况会有所不同(使用 VS2015 编译,我不确定它是否符合 C++14 标准(,它不创建两个对象,而只创建一个对象。顺序将是:

contruction of baseC
contruction of childC
destruction of childC
destruction of baseC

最后,将析构函数声明为虚拟始终是更安全和良好的做法。