我对虚拟析构函数和vtable有一些具体的问题。
假设我有以下代码:
class Base
{
public:
virtual ~Base();
};
class Child : public Base
{
public:
~Child();
};
问题:
答案 0 :(得分:3)
下面的解释假设编译器使用的虚拟调度实现基于虚拟表。
每个具有虚方法(声明或继承)的类都有自己的虚拟表。如果子类重写基类中的虚拟成员函数,则指向重写函数的指针将放在类的vtable中;否则,将保留指向基类实现的指针。
添加第一个虚函数会增加类实例的大小vtable指针的大小。第一个之后的虚函数不会增加实例的大小。
由于~Base
是虚拟的,~Child
也是虚拟的,即使省略virtual
关键字也是如此。如果是覆盖,virtual
关键字是可选的。
答案 1 :(得分:2)
通过new运算符创建Child类型的实例然后删除...将调用Base析构函数吗?
不在原始问题代码中,因为您没有从Child
继承Base
。
假设这是一个错误,并且我们修复了它,那么当你销毁~Base
即使它不是虚拟的时候会调用Child
,因为基类子对象被破坏了作为正常破坏序列的一部分。
虚拟析构函数的原因是您可以通过 Child
删除Base *
,并且仍然可以正确调用~Child
。
例如,用:
struct Base { ~Base(); };
struct Child: Base { ~Child(); };
struct VBase { virtual ~VBase(); };
struct VChild: VBase { ~VChild(); };
这适用于两个层次结构:
template <typename Derived>
void test_static() {
Derived d;
}
test_static<Child>(); // ~Child then ~Base invoked when d is destroyed
test_static<VChild>(); // ~VChild then ~VBase invoked when d is destroyed
但这仅适用于虚拟析构函数:
template <typename Derived, typename Base>
void test_dynamic() {
std::unique_ptr<Base> p(new Derived);
}
test_dynamic<Child, Base>; // only ~Base invoked when p destroyed
test_dynamic<VChild,VBase>; // ~VChild then ~VBase invoked as before.
对于 vtable 问题,它是一个实现细节,无论是否存在,如果存在,你不需要担心它。
答案 2 :(得分:1)
每个带有虚方法(声明/继承)的类都有自己的虚表(vtable)。当将虚拟方法(不是析构函数的任何方法)声明为虚拟方法时,派生类中该方法的所有替代都会自动成为虚拟方法。编译器还在具有虚拟方法的任何此类的开头添加_vptr。创建类的对象时,此_vptr将被填充,并将指向该类的vtable。虚拟析构函数的处理方式与任何其他虚拟函数相同。在您的示例中,由于〜Base是虚拟的,因此〜Child也将是虚拟的。
假设您这样做:
Child *child = new (class Child);
delete(child);
在这里,child-> _ vptr将指向Child的vtable。因此,在删除过程中,〜Child将首先被调用,随后被称为〜Base(按相反的构造顺序)。
或者,如果这样做,
Base *base = new (class Child);
delete(base);
在这里,base-> _ vptr将指向Child的vtable。因此,在删除过程中,将从vtable中首先调用〜Child,然后调用〜Base。
在gdb中,我们可以通过运行以下命令来验证_vptr
"info vtbl base" or "print *base"