#include &
using namespace std;

class Pet {
public:
int i;
virtual ~Pet(){
cout &

运行结果显示:

delete:Dog
delete:Pet

我认同调用Dog(派生类)中的析构函数,因为是虚函数,发生了重写覆盖,以防止内存泄漏。但我不理解,为何调用了Dog中的析构函数后,还要再调用Pet(基类)析构函数。


抛开虚函数,直接解答题主的疑问

如果你定义了一个局部对象Dog dog;,在它析构 ~Dog::Dog() 的时候,当然也需要析构父类,于是会隐式调用父类析构函数 ~Pet::Pet()。。。

所以,并不是子类析构函数没有override,而是子类析构函数override后,仍调用了父类析构函数。。。


首先,delete pd调用的是派生类的析构函数。

比如最直接的写法是:

Dog* pd = new Dog;
delete pd;

运行结果显示:

delete:Dog
delete:Pet

跟题主的日志是一样的。

为什么使用基类指针析构调用的是派生类的析构函数,是因为virtual析构。就是把子类的析构函数填充到类的虚表中,然后在调用析构函数时,去虚表中查到子类的析构函数,于是就调用子类的析构函数。

题主会有此问,是因为子类的析构函数其实不止只有析构子类的这些代码,C++语言隐式生成了很多其他代码,即析构顺序,在调用析构函数时会先调用子类的析构函数,然后反向依次析构子类的成员变数,然后再反向析构各基类,如果有多个基类,按照先非virtual基类后virtual基类的顺序。并且子类的析构函数不是立刻返回的,而是等到上述函数全部调用完才返回。

建议看下面的英文:

Destructors - cppreference.com

Destruction sequence

For both user-defined or implicitly-defined destructors, after the body of the destructor is executed, the compiler calls the destructors for all non-static non-variant members of the class, in reverse order of declaration, then it calls the destructors of all direct non-virtual base classes in reverse order of construction (which in turn call the destructors of their members and their base classes, etc), and then, if this object is of most-derived class, it calls the destructors of all virtual bases.Even when the destructor is called directly (e.g. obj.~Foo();), the return statement in ~Foo() does not return control to the caller immediately: it calls all those member and base destructors first.


怎么说呢,这是编译器的一个严谨处理。1、如果基类中的不是虚函数,派生类的函数就是静态决定的覆写。这种情况下,基类的就不再运行了。2、但如果基类中的是虚函数,派生类的函数理论上是运行时的调用。这种情况下,基类的虚函数有可能没有一个实现,也有可能有一个实现。在严谨考虑的情况下,编译器要运行一次这个虚函数。3、所以对于析构函数来说,最好的是写成虚函数,以确保析构函数的执行,释放基类申请的堆资源。

4、这事理解成一个约定俗成的常用做法即可。事实上,老是写这种:基类 *base = new 派生类() 这样的代码,虽然很常见,但是对编译器来说,类型转换次数多了之后,有很多难以确定的地方。


c++明确指出,当derived class对象由一个base class指针被删除,而base class带著一个non-virtual析构函数时,其结果未定义 — 一般来说,实际执行时对象的derived成分没被销毁,derived class的析构函数也没执行。也就是说我们得到了一个诡异的局部销毁对象

(Effective C++ Item7)


你猜猜 virtual 这个关键字在这里是什么用处?


设计者的思路啊,同构造函数顺序相反,不然你认为该如何处理合适呢。
这有点像数据结构中的栈操作,当实例化一个派生类对象时总是先调用其基类的构造函数,当删除一个指向基类的指针时总是先调用其派生类的析构函数。

这就是基类的析构函数需要定义为virtual的原因


先尝试理解多态(polymorphism) / 动态绑定(dynamic binding),答案就显而易见了


推荐阅读:
相关文章