网上有很多关于 VTable 的资源。他们通常对它们有相同的声明:
"每当一个类本身包含虚函数或覆盖父类的虚函数时,编译器都会为该类构建一个 vtable。这意味着并非所有类都有编译器为它们创建的 vtable。vtable 包含指向那个类中的虚函数。每个类只能有一个vtable,同一个类的所有对象都将共享同一个vtable。 ”
那么,为什么这意味着并非所有类都有编译器为它们创建的 vtable 呢?是因为 somc 类没有虚函数吗?
恰恰。有些类没有 vtable,因为它们没有任何虚拟方法。
虚拟方法是编译器无法为其生成直接调用的方法,因为它因类的实现而异。虚表是一种查找表,它通过在程序运行时延迟决定调用哪个实现来解决此问题:编译器不会生成函数调用,而是在虚表上生成方法查找,然后调用返回的方法.
举个例子:
class Foo
{
public:
virtual void vMethod()
{
std::cout << "Foo::vMethod was called!" << std::endl;
}
};
class Bar : public Foo
{
public:
virtual void vMethod()
{
std::cout << "Bar::vMethod was called!" << std::endl;
std::cout << "This is not the same as Foo::vMethod." << std::endl;
}
};
Foo* foo = new Bar;
foo->vMethod();
这将打印Bar
的消息。在大多数重要的场景中,您的编译器无法提前知道调用虚拟方法的对象的类型。如上所述,vtable 解决了这个问题,它提供了一个统一的查找机制来查找方法实现,无论对象的类型如何。
vtable 指针必须存在于类的每个实例中(这需要额外内存指针的大小,可能是 4 或 8 个字节),以及程序地址空间中某处的一些微不足道的静态内存。这对您来说可能看起来并不多(实际上很多人会同意),但这在某些情况下可能会很麻烦(例如内存极其有限的嵌入式系统)。为每个类设置 vtable 将违反一般 C++ 原则,即您只需为使用的内容付费,因此编译器不会在不需要时生成任何 vtable。
没有vtable具有禁用运行时间类型信息的显着副作用。如果您需要在代码中使用RTTI,您的类必须至少有一个虚拟方法。惯例是在这些情况下将析构函数标记为虚拟。
实际上,C++ 中的任何内容都不需要任何类具有 vtable - 这完全是一个实现问题。但是,具有虚函数的类必须以某种方式支持多态函数调用,这总是需要某种表/映射。该表/映射将由编译器为具有多态函数的类创建,并且可能(取决于编译器质量)为那些没有的类创建。
此外,某些类没有 vtable,因为已显式删除,请参阅__declspec(novtable)
(特定于编译器)