我试图通过编写一些示例代码并尝试遵循程序流来了解构造函数和析构函数的调用顺序。在大多数情况下,我能够理解(在Google需要的帮助下)。但是,在一个特殊情况下,我遇到了一些障碍。
这是我正在使用的程序:
#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:
编译器似乎首先创建一个基类和子类(这是预期的),然而它继续销毁这两个类,但它可以从基类调用成员函数并继续销毁基类再次上课。
如果有人能解释为什么按顺序调用这些函数,我将不胜感激。
答案 0 :(得分:3)
这里的问题是你正在切割对象。
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
,因为现在我们有一个指针动态调度启动并调用正确的虚函数。
答案 1 :(得分:0)
“异常”来自以下任务:
baseC c3 = childC();
首先创建一个临时childC
,从上到下按顺序调用构造函数:
Calling constructor of base class:
Calling constructor of child class:
然后进行分配,因此创建了一个baseC
对象。 但是这一次,它不是你的构造函数被调用,而是默认的复制构造函数。这就是为什么我们没有再次观察 { {1}}(用于构造对象Calling constructor of base class:
)。为了证明这一点,尝试将复制构造函数添加到baseC类:
c3
使用相同的主函数,您将在输出中观察两次句子:
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
仍在那里,调用了getName()方法,该方法输出:
c3
然后当变量Child Class
超出范围(c3
的结尾)时,main()
将被销毁:
c3
最后,Calling destructor of base class:
(使用VS2015编译,我不确定它是否符合C ++ 14标准)会有所不同,它不会创建两个对象而只会创建一个。那么顺序是:
baseC& c3 = ChildC();
最后,将析构函数声明为虚拟是一种更安全,更好的做法。