虚拟表通常被视为函数指针数组,尽管编译器也可以将数据指针(在多重继承和虚继承场景中或者用于类型信息) 、整数(用于修复)或哨兵元素(比如NULL指针)放入其中。布局通常是编译器特定的(或者多个C++编译器共享ABI),但只要正在编译的类具有稳定的接口,布局就是稳定的(否则你需要一直重新编译代码,这很麻烦)。还有一些额外的表格需要处理涉及虚拟和多重继承的边缘情况,并确保在派生类构造期间发生虚拟调用时能够按照标准进行操作(这就是下面输出中的VTT和构造表所用之处)。
至于GCC 4.x的具体情况: -fdump-class-hierarchy
开关的确像描述的那样(甚至更多)。我使用下面的示例代码在Coliru上测试了它:
struct Base
{
virtual ~Base() {}
virtual void f() = 0;
};
struct OtherBase
{
virtual ~OtherBase() {}
virtual void g() {}
};
struct Derived: public Base
{
virtual ~Derived() {}
virtual void f() {}
};
struct MultiplyDerived: public Base, public OtherBase
{
virtual ~MultiplyDerived() {}
virtual void f() {}
virtual void g() {}
};
struct OtherDerived: public Base
{
virtual ~OtherDerived() {}
virtual void f() {}
};
struct DiamondDerived: public Derived, public OtherDerived
{
virtual ~DiamondDerived() {}
virtual void f() {}
};
struct VirtuallyDerived: virtual public Base
{
virtual ~VirtuallyDerived() {}
virtual void f() {}
};
struct OtherVirtuallyDerived: virtual public Base
{
virtual ~OtherVirtuallyDerived() {}
virtual void f() {}
};
struct VirtuallyDiamondDerived: public VirtuallyDerived, public OtherVirtuallyDerived
{
virtual ~VirtuallyDiamondDerived() {}
virtual void f() {}
};
struct DoublyVirtuallyDiamondDerived: virtual public VirtuallyDerived, virtual public OtherVirtuallyDerived
{
virtual ~DoublyVirtuallyDiamondDerived() {}
virtual void f() {}
};
struct MixedVirtuallyDerived: virtual public Base, public OtherBase
{
virtual ~MixedVirtuallyDerived() {}
};
struct MixedVirtuallyDiamondDerived: public VirtuallyDerived, public MixedVirtuallyDerived
{
virtual ~MixedVirtuallyDiamondDerived() {}
virtual void f() {}
virtual void g() {}
};
struct VirtuallyMultiplyDerived: virtual public Base, virtual public OtherBase
{
virtual ~VirtuallyMultiplyDerived() {}
};
struct OtherVirtuallyMultiplyDerived: virtual public Base, virtual public OtherBase
{
virtual ~OtherVirtuallyMultiplyDerived() {}
};
struct MultiplyVirtuallyDiamondDerived: public VirtuallyMultiplyDerived, public OtherVirtuallyMultiplyDerived
{
virtual ~MultiplyVirtuallyDiamondDerived() {}
virtual void f() {}
virtual void g() {}
};
并且从G++收到(有关名称缩编的指南:TI是类型信息,TV是虚函数表,Th和Tv是在存在多继承和/或虚拟继承时用于进行正确虚函数调用的Thunk):
How may I assist you today?我看到的大多数编译器实现都是将基础对象“嵌入”到派生对象中。由于在引用被评估时相对偏移量将在编译时添加,所以vtable存储的位置变得无关紧要。
多重和虚拟继承更加复杂,可能需要根据正在访问的内容使用不同的偏移量。
我强烈推荐阅读Code Project上的这篇文章:不可思议的快速C++委托。它精彩地展示了不同编译器处理继承各个方面的广泛图景。如果您对不同编译器的低级工作原理感兴趣,那么这是一篇绝佳的文章。
编辑:我链接了错误的文章。已更正。