基析构函数如何调用派生析构函数



在下面的代码中,b 是一个基类指针。但是,当我调用析构函数(通过删除显式或隐式调用)时,首先调用派生类析构函数。我不明白这是如何工作的。可以有任意数量的派生类,每个派生类都有自己的析构函数。编译器如何知道要从基析构函数调用哪个派生类析构函数?

#include <iostream>
using namespace std;
class Base {
public:
    virtual ~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
    ~Derived() { cout << "Derived destructor" << endl; }
};
int main(int argc, char * argv[]) {
    Base * b = new Derived();
    b->~Base(); // delete b; has the same result
}

dynamic binding编译器不会决定,运行时会决定,因为析构函数是虚拟的。C++销毁调用当前类上的析构函数,并隐式调用父类,直到它命中基类。

对虚拟析构函数的调用与对任何其他虚拟函数的调用相同,这是通过虚拟表进行虚拟调度的结果。除此之外,

b->~Base(); // delete b; "has the same result"

这不是真的,因为delete也会释放内存,而您在这里没有这样做。 delete b调用析构函数进行*b,并将原始内存释放到操作系统。你只是摧毁了建筑物,但没有让地。

这与虚函数的完成方式相同。它称为动态绑定。当非虚拟成员函数在编译时静态解析意味着,虚拟成员在运行时动态解析意味着。编译器为此维护了一个 vtable。 如果对象具有一个或多个虚函数,编译器会在对象中放置一个称为"虚拟指针"或"v-pointer"的隐藏指针。此 V 指针指向称为"虚拟表"或"v-table"的全局表。从这里阅读更多细节。

它没有。它以相反的方式发生。普通虚函数调度调用派生析构函数,派生析构函数调用基析构函数。

注意:我的第一个答案太离谱了,所以我删除了它。它离基地太远了,有人应该投票反对我的回应。这是另一种尝试。

在开篇文章中,master_latch问道:

编译器如何知道要从基析构函数调用哪个派生类析构函数?

这种情况是如何发生的是特定于实现的。

为什么必须发生这种情况是"因为标准是这样说的"。以下是标准的内容:

C++11 12.4 第5段:

在执行析构函数的主体并销毁在主体中分配的任何自动对象之后,类 X 的析构函数调用 X 的直接非变量成员的析构函数、X 的直接基类的析构函数,如果 X 是派生最多的类的类型,则其析构函数调用 X 的虚拟基类的析构函数。调用所有析构函数时,就好像它们是使用限定名称引用的一样,也就是说,忽略更多派生类中任何可能的虚拟重写析构函数。基和成员的销毁顺序与其构造函数的完成顺序相反。析构函数中的 return 语句可能不会直接返回给调用方;在将控制权转移给调用方之前,将调用成员和基的析构函数。数组元素的析构函数按其构造的相反顺序调用。

C++11 12.4 第10段:

在显式析构函数调用中,析构函数名称显示为 ~,后跟表示析构函数类类型的类型名decltype 说明符。析构函数的调用受成员函数的常规规则的约束,...

C++11 12.4 第 10 段中的示例代码表明了上述意图:

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};
D D_object;
B* B_ptr = &D_object;
void f() {
  D_object.B::~B();   // calls B’s destructor
  B_ptr->~B();        // calls D’s destructor
  ...
}



master_latch,使用 b->~Base(); 的示例与示例代码中的第二次调用相同。把b->~Base();想象成b->__destruct_me()的意思。从某种意义上说,它与调用任何其他虚函数没有什么不同。

一个合规的实现必须这样做,因为"因为标准是这么说的"。实现是如何做到的?标准没有说。(顺便说一下,这是一个很好的要求。说出必须做的事情,但不要说如何去做。

大多数实现(我戳过的每个实现)都是通过为析构函数生成多个函数来实现的。一个函数实现程序员指定的析构函数的主体。包装析构函数执行析构函数的此主体,然后按构造的相反顺序销毁非静态数据成员,然后调用父类析构函数。类实际上可以从某个父类继承,这增加了另一个转折点。这意味着可能需要给定类的第三个析构函数。

那么实现如何知道b->~Base()应该为 class Derived 调用包装析构函数呢?将指向多态类的指针动态强制转换为 void* 指针将生成指向派生最多的对象的指针。

C++11 5.2.7 第7段:

如果T是"指向 cv void 的指针",则结果是指向 v 指向的最派生对象的指针。否则,将应用运行时检查以查看 v 指向或引用的对象是否可以转换为 T 指向或引用的类型。

换句话说,动态地将多态指针强制转换为 void* 会在声明或分配对象时生成指向对象的指针。虚拟表(不是标准的一部分)指示如何查找析构函数。该实现确保指向虚拟表的指针可以从指向最派生对象的void*指针确定。这就是让实现知道要调用哪个析构函数的原因。从那时起,指向最派生对象的指针不再是void*指针。

最新更新