一个类(child)继承了一个单一继承的基类和多重继承的base1和base2,通常需要多少个vptrs?如果一个对象有几个单一继承和多重继承,如何确定它有多少个vptrs?虽然标准没有规定vptrs,但我想知道实现如何实现虚函数。
一个类(child)继承了一个单一继承的基类和多重继承的base1和base2,通常需要多少个vptrs?如果一个对象有几个单一继承和多重继承,如何确定它有多少个vptrs?虽然标准没有规定vptrs,但我想知道实现如何实现虚函数。
你为什么在意呢?简单的答案是“足够”,但我猜你想要更完整的解释。
这不是标准的一部分,所以任何实现都可以自由地做出决定,但一个普遍的经验法则是,在使用虚表指针的实现中,作为第零近似,对于动态分发,你需要最多与添加新虚拟方法到层次结构中的类数目相同的虚拟表指针数量。 (在某些情况下,虚拟表可以被扩展,并且基类型和派生类型共享一个单一的“vptr”)
// some examples:
struct a { void foo(); }; // no need for virtual table
struct b : a { virtual foo1(); }; // need vtable, and vptr
struct c : b { void bar(); }; // no extra virtual table, 1 vptr (b) suffices
struct d : b { virtual bar(); }; // 1 vtable, d extends b's vtable
struct e : d, b {}; // 2 vptr, 1 for the d and 1 for b
struct f : virtual b {}; // 1 vptr, f reuse b's vptr to locate subobject b
struct g : virtual b {}; // 1 vptr, g reuse b's vptr to locate subobject b
struct h : f, g {}; // 2 vptr, 1 for f, 1 for g
// h can locate subobject b using f's vptr
b
的函数集中添加一个新的虚拟函数时,编译器将通过将新插槽附加到vtable的末尾来将两个潜在的表合并为一个单一的表,因此的vtable将是的vtable的扩展版本,其末尾有额外的元素,以维护二进制兼容性(即可以将 vtable解释为 vtable以访问中可用的方法),而对象将具有单个指针。
在多重继承的情况下,情况变得有些复杂,因为每个基类都需要与完整对象的子对象的布局相同,就好像它是一个单独的对象一样,因此会有额外的vptr指向完整对象的vtable中的不同区域。关于vptr/vtable的任何内容都没有具体说明,因此对于详细信息,这将取决于编译器,但几乎每个现代编译器都处理简单情况(我用“几乎”来防止万一)。
请您注意。
如果您从基类继承,并且它们有一个vptr,则自然会在您的类中拥有同样数量的继承vptr。
问题是:当编译器向已经有继承vptr的类添加vptr时,会发生什么?
编译器将尝试避免添加冗余的vptr:
struct B {
virtual ~B();
};
struct D : B {
virtual void foo();
};
这里B
有一个vptr,因此D
不会拥有自己的vptr,而是重用现有的vptr;B
的vtable通过添加一个foo()
项进行扩展。D
的vtable是从B
的vtable“派生”的,伪代码如下:
struct B_vtable {
typeinfo *info; // for typeid, dynamic_cast
void (*destructor)(B*);
};
struct D_vtable : B_vtable {
void (*foo)(D*);
};
再次声明:这是一个真实虚函数表的简化版,以便更好地理解。
对于非虚单一继承,各个编译器之间几乎没有太多差别。但是对于虚继承来说,不同编译器之间的差异就比较大了。
struct B2 : virtual A {
};
需要将 B2*
转换为 A*
,因此一个 B2
对象必须提供以下功能:
A*
成员offset_of_A_from_B2
offset_of_A_from_B2
通常情况下,一个类不会重用其虚基类的vptr(但是在非常特殊的情况下可以)。
struct d : b { virtual bar(); }; // 需要额外的虚函数表,需要 b.vptr 和 d.vptr
" 我认为没有编译器会在非虚基类中引入超过一个 vptr。 - curiousguy