2

我正在观看 BackToBasics 演讲:CppCon2019 的Virtual Dispatch 及其替代 方案。演示者说和幻灯片显示(假设我没有误解)派生类从基类继承了一个 vtable 指针,并且还具有自己的 vptr。

当然,从技术上讲,这不是标准规定的,但我让自己有点困惑,我对 sizeof() 的实验似乎也暗示应该只需要一个指针。请有人澄清一下是否有需要多个 vptr 的情况?

谢谢

PS 为了清楚起见,在这种情况下,我们正在考虑更常见的公共继承,而不是虚拟或多重继承(演讲者在演讲的前面部分明确提到了这一点)。

4

2 回答 2

2

vtable 包含该类的每个虚拟函数在已知偏移量处的地址。

[备注:在实践中,与常规类不同,vtable 具有负偏移量的成员,很像数组中间的指针。这只是一个不会太大改变实现自由的约定。无论如何,唯一的问题是在 vtable 中放置信息是由约定(ABI)规定的,编译器遵循相同的规则为多态类生成兼容的代码。]

当派生类中有附加函数时会发生什么?(不仅仅是从基类“继承”的函数)

一旦您接受指向结构的指针既指向整个对象又指向其第一个成员的想法,您就会认为指向派生类的指针指向适当位于偏移量零处的基类。因此,您可以拥有完全相同的指针值,表示为 a void*,可以在此约定下用于派生对象或基类,用于单继承

现在,您可以将其应用于任何数据结构,甚至应用于实际上不是表(相同类型的元素数组或可以以相同方式解释的值的数组)而是记录(不相关类型的对象或意义); 您可以看到,此类派生类的 vtable 可以以完全相同的方式从其唯一基类的 vtable 派生。

(请注意,如果将 C++ 编译为 C,则在执行此类操作时可能会遇到类型别名规则。当然汇编没有这样的问题,也没有天真的编译的“高级汇编程序”C。)

因此,对于单继承,基类被集成并优化到派生类中:

  • 对于实例的数据成员(类类型)
  • 对于虚函数成员,它是 vtable 的数据成员(或者你想象的元类的成员)。

请注意,将 base 放置在 offset 0 处允许您将 vtable base 放置在 offset 0 处,这反过来又允许您使用相同的 vptr 但并不暗示它;相反,与基础共享 vptr 意味着基础 vtable 位于偏移量零(vtable 布局 = 元类级别),因此基础必须位于偏移量零(数据成员布局 = 类级别)。

多重继承实际上是单继承加,因为一个类总是被视为特权:它放置在偏移量零处,因此指针相同,因此 vtable 可以放置在偏移量零处(因为指针相同);其他基地,并非如此。

正如我们所看到的,除了一个继承的多态类之外,所有继承的多态类都放置在多重继承中的非零偏移处。每个都在派生类中携带一个额外的“继承”vptr;该(隐藏)指针成员必须由任何派生构造函数正确填充。

这些额外的 vptr 用于发生在非零偏移量的基类,因此必须调整指向继承基的指针(添加一个正常量以转换为基指针,删除它以转换回来)。编译器需要生成代码来执行隐式转换是一件小事(将整数转换为浮点类型是一项复杂得多的任务);但这里的转换this是在给定基类型上的函数调用和降落在作为基类或派生类中的覆盖器的函数中:不同之处在于调整取决于仅对类(实例)已知的函数覆盖元类型)。所以vptr需要指向不同的vtable信息:知道如何处理这些基到派生指针转换的人。

作为“元类型”的实例,vtables 拥有自动调整所有指针的所有信息。(这些取决于所涉及的特定类类型,而不取决于其他因素。)

所以在实现层面,两种类型的继承是:

  • 零偏移继承;共享 vptr;在某些 vtable 和 ABI 描述中称为主要基类;
  • 任意偏移继承;拥有另一个 vptr;称为二级基类。

这是基本的东西。虚拟继承在实现层面要微妙得多,甚至主要的概念也不是很清楚,因为虚拟基只能在一些更多的派生类中成为派生类的“主要”!

于 2019-11-20T21:34:52.070 回答
1

假设我们有两个类,每个类至少有一个虚函数,Possession并且Vehicle. 要为其中任何一个派生类的实例调用这些虚拟函数,需要指向虚拟表的指针。由于这两个类是独立的,它们的虚拟表将完全不同。

现在想象它OwnedVehicle源自PossessionVehiclePossession要为 的实例调用虚函数,OwnedVehicle需要指向所需类型的虚函数表的指针PossessionVehicle类似地,为 的实例调用虚函数OwnedVehicle需要指向所需类型的虚函数表的指针Vehicle

典型的实现通过构建一个虚函数表来处理这个问题,该表OwnedVehicle包含一个部分用于OwnedVehicle虚拟功能(如果有的话),一个用于Vehicle虚拟功能,一个用于Possession虚拟功能。然后,当从指向不同类型对象的指针调用虚函数时,编译器所要做的就是将适用的增量应用于虚函数表指针以指向它的正确部分。

虽然多继承情况更复杂,但单继承也会发生同样的情况。的虚函数表在OwnedVehicle其中包含一个虚函数表,即使不涉及Vehicle也会这样做。Possession

于 2019-11-20T19:45:46.000 回答