派生类可以有多个指向虚拟表的指针

Can derived classes have more than one pointer to a virtual table?

本文关键字:虚拟 指针 派生      更新时间:2023-10-16

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

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

谢谢

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

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

[备注:实际上与常规类不同,vtables 的成员具有负偏移量,很像数组中间的指针。这只是一个公约,不会对实施自由产生太大影响。无论如何,唯一的问题是信息在vtable中的位置是由约定(ABI)立法的,编译器通过遵循相同的约定为多态类生成兼容的代码。

当派生类中有其他函数时会发生什么情况?(不仅仅是从基类"继承"的函数)

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

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

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

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

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

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

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

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

这些额外的 vptr 适用于在非零偏移量处发生的基类,因此必须调整指向继承基的指针(添加一个正常量以转换为基指针,删除它以转换回来)。编译器需要生成代码来执行隐式转换是一个微不足道的评论(将整数转换为浮点类型是一项更复杂的任务);但在这里,this的转换是在给定基类型的函数调用和登陆作为基类或派生类中的覆盖者的函数之间:不同之处在于调整取决于函数重写,而函数覆盖只知道一个类(元类型的实例)。因此,vptr 需要指向不同的 vtable 信息:一个知道如何处理这些基础到派生指针转换的信息。

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

因此,在实现级别,两种类型的继承是:

  • 零偏移继承; 共享 VPTR; 在某些 vtable 和 ABI 描述中称为主基类;
  • 任意偏移继承;具有另一个 VPTR;称为辅助基类。

这是基本的东西。虚拟继承在实现级别要微妙得多,甚至 primary 的概念也不是那么清楚,因为虚拟基只能在一些更派生的类中成为派生类的"主要"!

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

现在想象一下,OwnedVehicle既来自Possession,也源于Vehicle。要为OwnedVehicle实例调用Possession中的虚函数,需要一个指向Possession所需类型的虚函数表的指针。同样,要为OwnedVehicle实例调用Vehicle中的虚函数,需要一个指向Vehicle所需类型的虚函数表的指针。

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

虽然多重继承的情况更复杂,但仅单次继承也会发生同样的情况。OwnedVehicle的虚拟功能表包含用于Vehicle的虚拟功能表,即使不涉及Possession也会这样做。