在可怕的钻石中有一个单一的基础,两个中间对象从该基础派生,然后第四种类型关闭了钻石,并从中间级别的两种类型中获得了多重继承。
您的问题似乎是在前面的示例中声明了多少个函数?f
答案是一个。
让我们从简单的线性层次结构的例子开始:
struct base {
virtual void f() {}
};
struct derived : base {
virtual void f() {}
};
在此示例中,有一个f
声明有两个覆盖,base::f
并且derived::f
. 在类型的对象中derived
,最终的替代者是derived::f
. 需要注意的是,这两个f
函数都代表具有多个实现的单个函数。
现在,回到最初的例子,在右边的那行,Base::f
并且Right::f
以同样的方式被覆盖的同一个函数。所以对于一个类型的对象Right
,最终的覆盖是Right::f
. 现在对于 type 的最终对象Left
,最终的覆盖器是Base::f
asLeft
不会覆盖该函数。
当菱形关闭时,因为继承是virtual
有一个Base
对象,它声明了一个f
函数。在第二级继承中,Right
用自己的实现覆盖该函数,这是最派生类型的最终覆盖器Bottom
。
您可能想在标准之外查看它,并看看编译器实际上是如何实现的。编译器在创建对象时会添加一个指向虚拟表Base
的隐藏指针。vptr
虚拟表包含指向thunk的指针(为简单起见,假设该表包含指向函数最终覆盖器的指针,[1])。在这种情况下,该Base
对象将不包含任何成员数据,而只是一个指向包含指向函数指针的表的指针Base::f
。
当Left
extends时Base
,一个新的 vtable 被创建,Left
并且该 vtable 中的指针被设置为f
这个级别的最终覆盖,这是顺便Base::f
说一下,两个 vtable 中的指针(忽略蹦床)跳转到相同的实际实现。Left
在构造类型对象时,Base
首先初始化子对象,然后在初始化Left
(如果有的话)的成员之前Base::vptr
更新指针以引用Left::vtable
(即存储在中的指针Base
引用为 定义的表Left
)。
在菱形的另一侧,为其创建的 vtable包含Right
一个最终调用. 如果要创建一个类型的对象,则会发生相同的初始化过程,并且将指向.Right::f
Right
Base::vptr
Derived::f
现在我们得到最终的对象Bottom
。同样,为该类型生成一个 vtable,Bottom
并且该 vtable 与所有其他情况一样,包含一个表示f
. 编译器分析继承的层次结构,确定Right::f
overrides Base::f
,左分支上没有等价的override,所以在Bottom
's vtable中代表的指针指向f
了Right::f
。同样,在Bottom
对象的构造过程中,Base::vptr
更新以引用Bottom
的 vtable。
如您所见,所有四个 vtable 都有一个条目f
,程序中只有一个 f
条目,即使每个 vtable 中存储的值不同(最终覆盖器不同)。
[1] thunk是一小段代码,它this
在需要时调整指针(多重继承通常意味着它是需要的),然后将调用转发给实际的覆盖。在单继承的情况下,this
不需要更新指针,thunk消失,vtable 中的条目直接指向实际函数。