C++ 虚拟析构函数和 vtable
C++ virtual destructor & vtable
我有一些关于虚拟析构函数和 vtable 的具体问题。
假设我有以下代码:
class Base
{
public:
virtual ~Base();
};
class Child : public Base
{
public:
~Child();
};
问题:
- vtable 存储在哪里?它总是在基 class 中并且所有子 classes 只是保持指向它的指针吗?
- 添加虚方法只会将 sizeof(class) 增加 8 个字节,对吧? (假设 64 位系统)如果 base class 存储 table 怎么样?
- 正在通过 new 运算符创建 Child 类型的实例,然后删除...会调用 Base 析构函数吗? (我问是因为 Child class 的析构函数不是虚拟的……这是否意味着它只影响 Child 的 sub-class?)。
下面的解释假设编译器使用的虚拟分派实现是基于虚拟 tables。
每个带有虚方法(声明的或继承的)的 class 都有自己的虚方法 table。如果 subclass 覆盖基类中的虚拟成员函数,则指向覆盖函数的指针将放置在 class 的 vtable 中;否则,指向基础 class 实现的指针将保留在原地。
添加第一个虚函数会使class实例的大小增加vtable指针的大小。第一个之后的虚函数不会增加实例的大小。
因为 ~Base
是虚拟的,所以 ~Child
也是虚拟的,即使省略了 virtual
关键字。在覆盖的情况下,virtual
关键字是可选的。
Creating an instance of type Child via the new operator then delete ... will the Base destructor be called?
不在原始问题代码中,因为您没有 Child
继承自 Base
。
假设这是一个错误,我们修复了它,那么当你销毁一个 Child
时 ~Base
会被调用,即使它不是虚拟的,只是因为 base-class sub-object 作为正常销毁序列的一部分被销毁。
使用虚拟析构函数的原因是您可以通过 和 Base *
删除 Child
并且仍然可以正确调用 ~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的问题,是实现细节,有没有,如果有,在哪,大家不用管。
每个具有虚拟方法 (declared/inherited) 的 class 都有自己的虚拟 table(vtable)。当虚方法(任何,不仅仅是析构函数)被声明为虚方法时,派生的 classes 中该方法的所有重写都自动为虚方法。编译器还会在任何此类具有虚拟方法的 classes 的开头添加 _vptr。这个 _vptr 将在 class 的对象创建时被填充,并指向那个 class 的 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。所以在删除过程中,~Child 将首先被调用,然后是来自 vtable.
的 ~Base
在gdb中,我们可以通过运行这个命令来验证这个_vptr,
"info vtbl base" or "print *base"
我有一些关于虚拟析构函数和 vtable 的具体问题。
假设我有以下代码:
class Base
{
public:
virtual ~Base();
};
class Child : public Base
{
public:
~Child();
};
问题:
- vtable 存储在哪里?它总是在基 class 中并且所有子 classes 只是保持指向它的指针吗?
- 添加虚方法只会将 sizeof(class) 增加 8 个字节,对吧? (假设 64 位系统)如果 base class 存储 table 怎么样?
- 正在通过 new 运算符创建 Child 类型的实例,然后删除...会调用 Base 析构函数吗? (我问是因为 Child class 的析构函数不是虚拟的……这是否意味着它只影响 Child 的 sub-class?)。
下面的解释假设编译器使用的虚拟分派实现是基于虚拟 tables。
每个带有虚方法(声明的或继承的)的 class 都有自己的虚方法 table。如果 subclass 覆盖基类中的虚拟成员函数,则指向覆盖函数的指针将放置在 class 的 vtable 中;否则,指向基础 class 实现的指针将保留在原地。
添加第一个虚函数会使class实例的大小增加vtable指针的大小。第一个之后的虚函数不会增加实例的大小。
因为
~Base
是虚拟的,所以~Child
也是虚拟的,即使省略了virtual
关键字。在覆盖的情况下,virtual
关键字是可选的。
Creating an instance of type Child via the new operator then delete ... will the Base destructor be called?
不在原始问题代码中,因为您没有 Child
继承自 Base
。
假设这是一个错误,我们修复了它,那么当你销毁一个 Child
时 ~Base
会被调用,即使它不是虚拟的,只是因为 base-class sub-object 作为正常销毁序列的一部分被销毁。
使用虚拟析构函数的原因是您可以通过 和 Base *
删除 Child
并且仍然可以正确调用 ~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的问题,是实现细节,有没有,如果有,在哪,大家不用管。
每个具有虚拟方法 (declared/inherited) 的 class 都有自己的虚拟 table(vtable)。当虚方法(任何,不仅仅是析构函数)被声明为虚方法时,派生的 classes 中该方法的所有重写都自动为虚方法。编译器还会在任何此类具有虚拟方法的 classes 的开头添加 _vptr。这个 _vptr 将在 class 的对象创建时被填充,并指向那个 class 的 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。所以在删除过程中,~Child 将首先被调用,然后是来自 vtable.
的 ~Base在gdb中,我们可以通过运行这个命令来验证这个_vptr,
"info vtbl base" or "print *base"