Microsoft编译器允许您在声明类时为“ __declspec”属性添加“ novtable”扩展名。
声明的目标是显着减少所生成代码的大小。 在使用我们的组件进行的实验中,减少了DLL大小的0.6%至1.2%。
适用性:并非旨在直接从它们实例化的类。
例如:纯接口类。
在代码中,它看起来像这样:
struct __declspec(novtable) IDrawable { virtual void Draw() const = 0; };
注意:struct关键字用于声明一个接口类,以消除不相关的文章详细信息的示例; 而在使用类的情况下,则必须使用public来表示方法的“公共性”。 出于同样的原因,我不会在本文中向接口类添加虚拟析构函数。名称“ novtable”承诺将没有虚拟表。但是,调用虚拟函数的机制在以下代码中如何工作:
回顾在类中声明虚函数时添加的内容:
- 定义虚拟函数表。 该表的一个实例用于该类的所有实例。
- 指向虚拟功能表的指针将添加到类数据成员。
- 在类的构造函数中初始化此指针的代码。
因此,在我们的示例中,将声明两个虚拟函数表:用于IDrawable和用于Rectangle。 创建Rectangle对象时,将首先执行IDrawable构造函数,该构造函数将初始化指向其虚拟函数表的指针。 从示意图上看,它看起来像这样:

由于IDrawable中的draw函数被声明为纯虚函数(指示“ = 0”,而不是函数的主体),因此,将由编译器生成的purecall函数的地址写入虚函数表中。
然后执行Rectangle构造函数,该构造函数初始化相同的指针,但指向其虚函数表:

“ novtable”有什么作用?为什么微软承诺减少代码的大小?
IDrawable虚拟函数表的不必要定义和IDrawable构造函数中指向它的指针的初始化在添加“ novtable”时从结果代码中排除。
在这种情况下,构造IDrawable时,指向虚拟函数表的指针将包含不可预测的值。 但这不应该困扰我们,因为在对象的完整构造之前创建一个可以访问虚拟功能的实现通常是一个错误。 例如,如果在基类的构造函数中调用了此类的非虚拟函数,而该构造函数又调用了虚函数,那么纯调用函数将在没有novtable的情况下被调用,而具有novtable的不可预测的行为; 所有选项都不可接受。
注意,不仅大小减小,而且程序的加速。
RTTI
如您所知,std :: dynamic_cast允许您将指针和链接从类的一个实例转换为指针,再将其链接到另一实例,如果这些类是分层链接且是多态的(包含虚拟函数表)。 反过来,typeid运算符允许您使用传递给对象的指针(链接)在运行时获取有关该对象的信息。 这些功能由RTTI机制提供,该机制使用参考类vtable定位的类型信息。 结构和位置的详细信息取决于编译器。 对于Microsoft编译器,它看起来像这样:

因此,如果在编译过程中命令编译器启用RTTI,则novtable还将排除IDrawable的type_info定义及其所需的服务数据的创建。
请注意,如果您以某种方式提供了知识,即指向基类的可简化指针(链接)指示派生类的实现,则std :: static_cast效率更高,并且不需要RTTI。
特定于Microsoft
在Windows下编译时,除了MSVC外,Clang中还具有相同语法的此功能。
结论
- __declspec(novtable)-不影响类实例占用的内存量。
- 通过消除未使用的虚函数表的定义,RTTI开销以及消除接口类构造函数中指向虚函数表的指针的初始化代码,可以确保减小程序的大小并加快程序的速度。