一个对象的 clas(child) 与一个多重继承 base1 和 base2 的基类具有单继承关系,通常需要多少个 vptr。确定一个对象提供了多少个 vptr 的策略是什么,它具有一对单继承和多继承。虽然标准没有具体说明 vptrs 但我只想知道一个实现是如何实现虚拟功能的。
2 回答
你为什么在乎?简单的答案就足够了,但我想你想要更完整的东西。
这不是标准的一部分,因此任何实现都可以随心所欲地做,但一般的经验法则是,在使用虚拟表指针作为第零近似值的实现中,您最多需要作为许多指向虚拟表的指针,因为有向层次结构添加新虚拟方法的类。(在某些情况下可以扩展虚拟表,并且基类型和派生类型共享一个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
基本上,需要自己的动态调度(不能直接重用父对象)的类型的每个子对象都需要自己的虚拟表和 vptr。
实际上,编译器将不同的 vtable 合并到一个 vtable 中。当d
在 中的函数集上添加新的虚函数时b
,编译器将通过将新插槽附加到 vtable 的末尾来将潜在的两个表合并为一个表,因此 vtable ford
将是 vtable for 的扩展版本b
最后有额外的元素保持二进制兼容性(即d
vtable 可以解释为b
vtable 以访问 中可用的方法b
),并且d
对象将具有单个vptr
.
在多重继承的情况下,事情变得有点复杂,因为每个基础都需要与完整对象的子对象具有相同的布局,而不是单独的对象,因此会有额外的 vptr 指向完整对象的不同区域虚表。
最后,在虚拟继承的情况下,事情变得更加复杂,同一个完整对象可能有多个 vtables,vptr 会随着构造/破坏的发展而更新(vptr 总是随着构造/破坏的发展而更新,但没有虚拟继承vptr 将指向基础的 vtables,而在虚拟继承的情况下,同一类型将有多个 vtables)
精美的印刷品
没有指定任何关于 vptr/vtable 的内容,所以这将取决于编译器的细节,但是几乎每个现代编译器都处理简单的情况(我写“几乎”以防万一)。
你被警告了。
对象布局:非虚继承
如果你从基类继承,并且它们有一个 vptr,那么你的类中自然会有尽可能多的继承 vptr。
问题是:编译器何时将 vptr 添加到已继承 vptr 的类中?
编译器会尽量避免添加多余的 vptr:
struct B {
virtual ~B();
};
struct D : B {
virtual void foo();
};
这里B
有一个vptr,所以D
没有得到自己的vptr,它重用了已有的vptr;的 vtableB
扩展为foo()
. vtable forD
是从 vtable for 中“派生的” B
,伪代码:
struct B_vtable {
typeinfo *info; // for typeid, dynamic_cast
void (*destructor)(B*);
};
struct D_vtable : B_vtable {
void (*foo)(D*);
};
小字,再次:这是一个真正的 vtable 的简化,以获得这个想法。
虚拟继承
对于非虚拟单继承,实现之间几乎没有变化的余地。对于虚拟继承,编译器之间有更多的变化。
struct B2 : virtual A {
};
存在从B2*
to的转换A*
,因此B2
对象必须提供以下功能:
- 要么与
A*
成员 - 要么有一个 int 成员:
offset_of_A_from_B2
- 要么使用它的 vptr,要么通过存储
offset_of_A_from_B2
在 vtable 中
通常,一个类不会重用其虚拟基类的 vptr(但在非常特殊的情况下可以)。