除了“vtables 是特定于实现的”(它们是)之外,如果使用 vtable:每个类都会有唯一的 vtables。即使B::f和C::f没有声明为虚拟的,因为在基类(代码中的A )的虚拟方法上有匹配的签名, B::f和C::f都是隐式虚拟的. 因为每个类至少有一个唯一的虚方法(B::f覆盖B实例的 A::f 和C :: f类似的C实例),您需要三个 vtable。
您通常不应该担心这些细节。重要的是您是否有虚拟调度。 您不必通过显式指定要调用的函数来使用虚拟调度,但这通常仅在实现虚拟方法时有用(例如调用基类的方法)。例子:
struct B {
virtual void f() {}
virtual void g() {}
};
struct D : B {
virtual void f() { // would be implicitly virtual even if not declared virtual
B::f();
// do D-specific stuff
}
virtual void g() {}
};
int main() {
{
B b; b.g(); b.B::g(); // both call B::g
}
{
D d;
B& b = d;
b.g(); // calls D::g
b.B::g(); // calls B::g
b.D::g(); // not allowed
d.D::g(); // calls D::g
void (B::*p)() = &B::g;
(b.*p)(); // calls D::g
// calls through a function pointer always use virtual dispatch
// (if the pointed-to function is virtual)
}
return 0;
}
一些可能有所帮助的具体规则;但不要引用我的话,我可能错过了一些边缘情况:
- 如果一个类有虚方法或虚基,即使继承,实例也必须有一个虚表指针。
- 如果一个类声明了非继承的虚方法(例如当它没有基类时),那么它必须有自己的 vtable。
- 如果一个类的覆盖方法集与其第一个基类不同,那么它必须有自己的 vtable,并且不能重用基类。(析构函数通常需要这个。)
- 如果一个类有多个基类,而第二个或以后的基类有虚方法:
- 如果早期的基础没有虚方法并且空基础优化应用于所有早期的基础,则将此基础视为第一个基类。
- 否则,该类必须有自己的 vtable。
- 如果一个类有任何虚拟基类,它必须有自己的 vtable。
请记住,vtable 类似于类的静态数据成员,并且实例只有指向它们的指针。
另请参阅 Jan Gray 撰写的综合文章C++:Under the Hood(1994 年 3 月)。(如果该链接失效,请尝试谷歌。)
重用 vtable 的示例:
struct B {
virtual void f();
};
struct D : B {
// does not override B::f
// does not have other virtuals of its own
void g(); // still might have its own non-virtuals
int n; // and data members
};
特别是,注意B的 dtor 不是虚拟的(这可能是实际代码中的错误),但在此示例中,D实例将指向与B实例相同的 vtable 。