注意:我的第一个答案太离谱了,所以我删除了它。这太离谱了,以至于有人应该对我的回答投反对票。这是另一个尝试。
在开场白中,master_latch 问道:
编译器如何知道从基析构函数调用哪个派生类析构函数?
这是如何发生的取决于具体实现。
为什么会发生这种情况是“因为标准是这样说的”。标准是这样说的:
C++11 12.4 第 5 段:
在执行析构函数的主体并销毁主体内分配的任何自动对象后,类 X 的析构函数调用 X 的直接非变体成员的析构函数,X 的直接基类的析构函数,如果 X 是类型在最派生类中,它的析构函数调用 X 的虚拟基类的析构函数。所有的析构函数都被调用,就好像它们被一个限定名引用一样,也就是说,忽略更多派生类中任何可能的虚拟覆盖析构函数。基和成员按照其构造函数完成的相反顺序被销毁。析构函数中的 return 语句可能不会直接返回给调用者;在将控制权转移给调用者之前,会调用成员和基的析构函数。数组元素的析构函数按其构造的相反顺序调用。
C++11 12.4 第 10 段:
在显式析构函数调用中,析构函数名称显示为 ~ 后跟 type-name 或 decltype-specifier,表示析构函数的类类型。析构函数的调用遵循成员函数的一般规则,...
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* 指针。