C ++虚拟析构函数&虚函数表

时间:2017-01-16 17:33:39

标签: c++ polymorphism

我对虚拟析构函数和vtable有一些具体的问题。

假设我有以下代码:

class Base
{
public:

    virtual ~Base();

};

class Child : public Base
{
public:

    ~Child();
};

问题:

  1. vtable存放在哪里?它总是在基类中,所有子类只是保持指向它的指针吗?
  2. 添加虚方法只会将sizeof(class)增加8个字节吗? (假设64位系统)如果存储表,基类怎么样?
  3. 通过new运算符创建Child类型的实例然后删除...将调用Base析构函数吗? (我问,因为Child class的析构函数不是虚拟的......这是否意味着它只会影响Child的子类?)。

3 个答案:

答案 0 :(得分:3)

下面的解释假设编译器使用的虚拟调度实现基于虚拟表。

  1. 每个具有虚方法(声明或继承)的类都有自己的虚拟表。如果子类重写基类中的虚拟成员函数,则指向重写函数的指针将放在类的vtable中;否则,将保留指向基类实现的指针。

  2. 添加第一个虚函数会增加类实例的大小vtable指针的大小。第一个之后的虚函数不会增加实例的大小。

  3. 由于~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"