为什么vptr不是静态的?

Har*_*rya 13 c++ virtual function vtable vptr

包含一个或多个虚函数的每个类都有一个与之关联的Vtable.一个名为vptr的void指针指向该vtable.该类的每个对象都包含指向同一Vtable的vptr.那为什么vptr不是静态的呢?而不是将vptr与对象相关联,为什么不将它与类关联?

在此输入图像描述

NPE*_*NPE 9

对象的运行时类是对象本身的属性.实际上,vptr表示运行时类,因此不能static.但是,它指向的内容可以由同一运行时类的所有实例共享.

  • @HarshMaurya编译器如何知道调用哪个函数?使用`vptr`的重点是编译器不知道实际的类型; 实际类型可以在运行时变化. (5认同)
  • @HarshMaurya,如果你有'A&a`那个对象的"运行时类"是什么?`A`?`A1`?`A2`?别的什么?你怎么知道的?调用`A`的静态函数永远不能告诉你`a`的运行时类(正确的术语是"动态类型") (3认同)
  • @HarshMaurya:这会产生鸡和蛋的问题.要调用`getType`函数,首先需要虚拟表指针(因为它必须是`virtual`才能工作).但是要获取虚拟表指针,建议我们调用`getType`.哎呀. (2认同)

Jon*_*ely 5

你的图有误。没有一个 vtable,每个多态类型都有一个 vtable。vptr forA指向 vtable for A, vptr forA1指向 vtable forA1等。

鉴于:

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();
};
Run Code Online (Sandbox Code Playgroud)

对于虚函数表A包含{ &A::foo, &A::bar }
了V表A1中包含{ &A1::foo, &A::bar }
的虚函数表的A2包含{ &A2::foo, &A::bar }
的虚函数表的A3包含{ &A::foo, &A3::bar, &A3::baz }

所以当你调用a.foo()编译器时会跟随对象的 vptr 来查找 vtable 然后调用 vtable 中的第一个函数。

假设一个编译器使用你的想法,我们写:

A1 a1;
A2 a2;
A& a = (std::rand() % 2) ? a1 : a2;
a.foo();
Run Code Online (Sandbox Code Playgroud)

编译器在基类中A查找并找到该类的 vptr,该类A(根据您的想法)是static该类型的属性,而A不是引用a绑定到的对象的成员。该 vptr 是否指向 vtable for A, or A1or A2or 或其他东西?如果它指向 vtable 因为A1它在a引用时有 50% 的时间是错误的a2,反之亦然。

现在假设我们写:

A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();
Run Code Online (Sandbox Code Playgroud)

aaa都是对 的引用A,但它们需要两个不同的 vptr,一个指向 vtable for A1,一个指向 vtable for A2。如果 vptr 是一个静态成员,A它如何一次具有两个值?唯一合乎逻辑的、一致的选择是静态 vptr ofA指向 vtable for A

但这意味着a.foo()调用A::foo()在它应该调用的时候调用A1::foo(),并且调用aa.foo()也在A::foo()它应该调用的时候调用A2::foo()

显然,您的想法未能实现所需的语义,证明使用您的想法的编译器不能是 C++ 编译器。有没有办法让编译器来获取虚函数表的A1a没有任何知道什么是派生类型是(这是一般不可能,参考-底部可能已经从不同的库中定义的函数返回,可能指尚未编写的派生类型!)或将 vptr 直接存储在对象中。

vptr 必须a1a2and不同,并且必须在不知道动态类型的情况下通过指针或对基类的引用访问它们时可以访问,这样当您通过对基类的引用获得 vptr 时a,它仍然指向右侧vtable,而不是基类 vtable。最明显的方法是将 vptr 直接存储在对象中。另一种更复杂的解决方案是将对象地址映射到 vptrs,例如类似的东西std::map<void*, vtable*>,并a通过查找找到 vtable&a,但这仍然为每个对象存储一个 vptr,而不是每个类型一个,并且每次创建和销毁多态对象时都需要更多的工作(和动态分配)来更新映射,并且会增加整体内存使用量,因为映射结构会占用空间。将 vptr 嵌入对象本身更简单。