虚拟决赛覆盖器
Virtual final overrider
在试图更深入地分析C++的继承机制时,我偶然发现了以下示例:
#include<iostream>
using namespace std;
class Base {
public:
virtual void f(){
cout << "Base.f" << endl;
}
};
class Left : public virtual Base {
};
class Right : public virtual Base{
public:
virtual void f(){
cout << "Right.f" << endl;
}
};
class Bottom : public Left, public Right{
};
int main(int argc,char **argv)
{
Bottom* b = new Bottom();
b->f();
}
上面的,不知何故,编译并调用了Right::f()。我看到编译器中可能发生了什么,它理解有一个共享的 Base 对象,并且 Right 覆盖了 f(),但实际上,在我的理解中,应该有两种方法:Left::f()
(继承自 Base::f()
)和 Right::f()
,它覆盖了Base::f()
。现在,我认为,基于Bottom继承了两种不同的方法,它们都具有相同的签名,因此应该存在冲突。
谁能解释一下C++的哪个规范细节处理这种情况,以及它是如何从低级角度处理的?
在可怕的钻石中,有一个基底,两个中间对象从中派生出来,然后第四种类型通过中间层中两种类型的多重继承来关闭钻石。
您的问题似乎是在前面的示例中声明了多少个f
函数?答案是一个。
让我们从更简单的线性层次结构示例开始,该线性层次结构仅包含 base 和 派生:
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
。现在对于类型 Left
的最终对象,最终覆盖器被Base::f
,因为Left
不会覆盖该函数。
当菱形闭合时,由于继承是virtual
因此存在单个Base
对象,该对象声明单个f
函数。在第二级继承中,Right
使用自己的实现覆盖该函数,这是派生最多的类型Bottom
的最终覆盖程序。
您可能希望在标准之外查看这一点,并查看编译器如何实际实现这一点。编译器在创建Base
对象时,它会添加一个隐藏指针vptr
到虚拟表。虚拟表包含指向 thunk 的指针(为简单起见,假设该表包含指向函数最终覆盖器的指针 [1])。在这种情况下,Base
对象将不包含任何成员数据,而只包含一个指向表的指针,该表包含指向函数Base::f
的指针。
当Left
扩展Base
时,会为Left
创建一个新的 vtable,并且该 vtable 中的指针被设置为此级别的 f
的最终覆盖器,顺便Base::f
,因此两个 vtable 中的指针(忽略蹦床)跳转到相同的实际实现。构造类型 Left
的对象时,首先初始化 Base
子对象,然后在初始化 Left
的成员(如果有)之前,Base::vptr
指针更新为引用Left::vtable
(即存储在 Base
中的指针引用为 Left
定义的表)。
在钻石的另一侧,为Right
创建的 vtable 包含一个最终调用 Right::f
的 thunk 。如果要创建类型为 Right
的对象,将发生相同的初始化过程,并且Base::vptr
将指向 Derived::f
。
现在我们到了最终对象 Bottom
.同样,为类型 Bottom
生成一个 vtable,并且该 vtable(与所有其他 vtable 一样)包含一个表示 f
的条目。编译器分析继承的层次结构并确定Right::f
覆盖Base::f
,并且在左分支上没有等效的覆盖,因此在Bottom
的 vtable 中,表示f
的指针指的是Right::f
。同样,在构造Bottom
对象期间,Base::vptr
会更新以引用Bottom
的 vtable。
如您所见,所有四个 vtable 都有一个 f
条目,程序中只有一个f
,即使存储在每个 vtable 中的值不同(最终覆盖器不同)。
[1] thunk 是一小段代码,它在需要时调整this
指针(多重继承通常意味着需要它),然后将调用转发到实际覆盖。在单一继承的情况下,this
指针不需要更新,并且 thunk 消失,vtable 中的条目直接指向实际函数。
- 确保模拟的 GTest 方法覆盖虚拟方法
- 为什么我无法覆盖虚拟功能?
- 使用不同的返回类型覆盖虚拟函数
- C++ 使用不同的返回类型覆盖虚拟函数
- 通过覆盖虚拟函数来获取'unresolved external symbol'
- 部分覆盖虚拟函数
- 用现有函数覆盖虚拟函数
- 覆盖虚拟函数 - 派生类具有不同的参数
- 在模板类中覆盖虚拟函数的错误
- 覆盖虚拟函数时出错C++
- C :从模板类得出的覆盖虚拟纯函数
- 覆盖虚拟函数不起作用,头文件和C ++文件
- 方法是否覆盖虚拟
- 如何正确覆盖虚拟方法以添加功能
- 为什么子类覆盖虚拟函数不能更改父类的默认函数参数
- 为什么C++显式实例化的模板方法不能覆盖虚拟方法?
- 如何用非虚拟函数覆盖虚拟函数
- 非覆盖虚拟函数的绑定类型
- 我可以用纯虚拟函数覆盖虚拟函数吗?
- 使用更多参数覆盖虚拟函数