你为什么在乎?简单的答案是enough,但我猜你想要更完整的东西。
这不是标准的一部分,因此任何实现都可以自由地按照自己的意愿进行,但一般的经验法则是,在使用虚拟表指针的实现中,作为第零个近似值,对于动态调度,您最多需要许多指向虚拟表的指针,因为有一些类add层次结构的新虚拟方法。 (在某些情况下,虚拟表可以扩展,基类和派生类型共享一个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 的末尾来将潜在的两个表合并为一个表,因此 vtabled
将是 vtable 的扩展版本b
最后有额外的元素保持二进制兼容性(即d
vtable 可以解释为b
vtable 来访问可用的方法b
),以及d
对象将有一个vptr
.
在多重继承的情况下,事情会变得更加复杂,因为每个基需要与完整对象的子对象具有相同的布局,而不是单独的对象,因此会有额外的 vptr 指向完整对象中的不同区域。虚拟表。
最后,在虚拟继承的情况下,事情变得更加复杂,并且同一个完整对象可能有多个 vtable,并且 vptr 会随着构造/析构的发展而更新(vptr 总是随着构造/析构的发展而更新,但没有虚拟继承, vptr 将指向基类的 vtable,而在虚拟继承的情况下,同一类型将有多个 vtable)