你的图表是错误的。没有一个 vtable,每个多态类型都有一个 vtable。 A的vptr指向A的vtable,A1的vptr指向A1的vtable等。
给定:
class A {
public:
virtual void foo();
virtual void bar();
};
class A1 : public A {
virtual void foo();
};
class A2 : public A {
virtual void foo();
};
class A3 : public A {
virtual void bar();
virtual void baz();
};
A 的 vtable 包含 { &A::foo, &A::bar }
A1 的 vtable 包含 { &A1::foo, &A::bar }
A2 的 vtable 包含 { &A2::foo, &A::bar }
A3 的 vtable 包含 { &A::foo, &A3::bar, &A3::baz }
所以当你调用 a.foo() 时,编译器会跟随对象的 vptr 找到 vtable,然后调用 vtable 中的第一个函数。
假设一个编译器使用了你的想法,我们写:
A1 a1;
A2 a2;
A& a = (std::rand() % 2) ? a1 : a2;
a.foo();
编译器在基类A 中查找并找到A 类的vptr,它(根据您的想法)是static 类型的static 属性而不是对象的成员引用a 绑定到。该 vptr 是否指向 A、A1 或 A2 或其他什么的 vtable?如果它指向A1 的vtable,那么当a 引用a2 时,50% 的时间都是错误的,反之亦然。
现在假设我们写:
A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();
a 和aa 都是对A 的引用,但是它们需要两个不同的vptr,一个指向A1 的vtable,一个指向A2 的vtable。如果 vptr 是 A 的静态成员,它怎么能同时具有两个值?唯一合乎逻辑且一致的选择是A 的静态vptr 指向A 的vtable。
但这意味着调用a.foo()在应该调用A1::foo()时调用A::foo(),并且调用aa.foo()在它应该调用A2::foo()时也调用A::foo()。
很明显,您的想法未能实现所需的语义,证明使用您的想法的编译器不能是 C++ 编译器。编译器无法在不知道派生类型是什么的情况下从 a 获取 A1 的 vtable(这通常是不可能的,对基址的引用可能已经从定义在不同的库,并且可以引用尚未编写的派生类型!)或将 vptr 直接存储在对象中。
a1和a2的vptr必须不同,并且在通过指针或对base的引用访问它们时必须在不知道动态类型的情况下可访问,这样当您通过对base的引用获取vptr时类,a,它仍然指向正确的 vtable,而不是基类 vtable。最明显的方法是将 vptr 直接存储在对象中。另一种更复杂的解决方案是将对象地址映射到 vptrs,例如类似于std::map<void*, vtable*>,并通过查找&a 找到a 的vtable,但这仍然为每个对象存储一个vptr,而不是每种类型一个,并且需要更多的工作(和动态分配)来更新地图每次创建和销毁多态对象时,都会增加整体内存使用量,因为映射结构会占用空间。将 vptr 嵌入对象本身会更简单。