在下面的代码中,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
}
答案 0 :(得分:1)
dynamic binding
,编译器没有决定,运行时是因为析构函数是虚拟的。 C ++破坏调用当前类的析构函数,并隐式调用父类,直到它到达基类。
答案 1 :(得分:1)
对虚拟析构函数的调用与对任何其他虚函数的调用相同,这是通过虚拟表进行虚拟调度的结果。除此之外,
b->~Base(); // delete b; "has the same result"
这不是真的,因为delete
也释放了你在这里没有做过的记忆。 delete b
调用*b
的析构函数并将原始内存释放到操作系统。你只是摧毁了这座建筑物,但没有给地面带回来。
答案 2 :(得分:1)
这与虚拟功能的完成方式相同。它叫做动态绑定。静态解析非虚拟成员函数意味着在编译时,虚拟成员在运行时动态解析。编译器为此维护一个vtable。如果对象具有一个或多个虚函数,则编译器会在对象中放置一个名为“虚指针”或“v指针”的隐藏指针。该v指针指向称为“虚拟表”或“v表”的全局表。详细了解here。
答案 3 :(得分:1)
没有。它反过来发生了。普通虚函数调度调用派生的析构函数,派生的析构函数调用基础析构函数。
答案 4 :(得分:0)
注意:我的第一个答案是如此偏离基础,我删除了它。到目前为止,有人应该拒绝投票给我的回复。这是另一种尝试。
在开幕式上,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
是“指向 cvvoid
的指针”,则结果是指向v
指向的派生程度最大的对象的指针。否则,将应用运行时检查以查看v
指向或引用的对象是否可以转换为T
指向或引用的类型。
换句话说,动态地将多态指针转换为void*
会在声明或分配对象时生成指向该对象的指针。虚拟表(不是标准的一部分)规定了如何查找析构函数。该实现确保可以从指向最派生对象的void*
指针确定指向虚拟表的指针。这就是让实现知道要调用哪个析构函数的原因。从那时起,指向该派生对象的指针不再是void*
指针。