虚拟多重继承-最终重写器 在试图更深入地分析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(); } #包括 使用名称空间std; 阶级基础{ 公众: 虚空f(){ cout
在可怕的钻石中有一个单一的基,两个中间对象从中派生,然后第四个类型通过中间级别中两个类型的多重继承关闭钻石 您的问题似乎是在前面的示例中声明了多少个虚拟多重继承-最终重写器 在试图更深入地分析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(); } #包括 使用名称空间std; 阶级基础{ 公众: 虚空f(){ cout,c++,multiple-inheritance,virtual-inheritance,C++,Multiple Inheritance,Virtual Inheritance,在可怕的钻石中有一个单一的基,两个中间对象从中派生,然后第四个类型通过中间级别中两个类型的多重继承关闭钻石 您的问题似乎是在前面的示例中声明了多少个f函数?答案是一个 让我们从一个简单的例子开始,这个简单的例子是一个线性层次结构,它只包含基和派生: struct base { virtual void f() {} }; struct derived : base { virtual void f() {} }; 在此示例中,声明了一个f,其中有两个重写,base::f和deriv
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
。现在对于Left
类型的最终对象,最终重写器是Base::f
asLeft
不会覆盖该功能
当菱形关闭时,由于继承是虚拟的
,因此有一个Base
对象,它声明一个f
函数。在继承的第二级中,Right
用它自己的实现重写该函数,这是最派生的底部类型的最终重写器代码>
您可能希望在标准之外了解这一点,并了解编译器如何实际实现这一点。编译器在创建Base
对象时,会向虚拟表添加一个隐藏指针vptr
。虚拟表包含指向thunks的指针(为了简单起见,只需假设表中包含指向函数最终重写器的指针[1])。在这种情况下,Base
对象将不包含成员数据,而只包含指向表的指针,该表包含指向函数Base::f
的指针
当Left
扩展Base
时,将为Left
创建一个新的vtable,该vtable中的指针设置为该级别的f
的最终重写器,顺便说一句,它是Base::f
,因此两个vtable中的指针(忽略蹦床)跳转到相同的实际实现。当构造类型为Left
的对象时,首先初始化Base
子对象,然后在初始化Left
的成员之前(如果有)更新Base::vptr
指针以引用Left::vtable
(即,存储在Base
中的指针指的是为Left
定义的表)
在菱形的另一边,为Right
创建的vtable包含一个thunk,该thunk最终调用Right::f
。如果要创建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是一小段代码,如果需要,它可以调整这个指针(多重继承通常意味着需要它)然后将调用转发到实际重写。在单继承的情况下,此
指针不需要更新,thunk消失,vtable中的条目直接指向实际函数。问题不完全在于此。这里只有一个类重写方法并编译,而mon不会编译new Bottom(23);
,在您的示例中,您没有接受int
的构造函数。继承是一个不好的词。忘记它吧。没有继承。永不。好吧,因此Bottom
对象关闭菱形,并查看具有函数f()的基类已定义。现在存在两种实现Base::f()和Right::f(),现在基于接口层次结构选择Right::f()。这准确吗?@Bober02:它足够接近除自己实现编译器之外的所有目的:)我已经更新了答案,其中包含了您可能想查看的实现细节。您在回答中提到,当创建Left时,它的基本对象vtable指向Left vtable。这是因为只有一个虚拟方法吗?如果在base中也有一个非虚拟方法,它将有一个单独的vtable right?另外,您说tre是一个f函数,但不同的重写器。我只看到2:Base::f和Right::f@Bober02:非虚拟函数不会影响vtable。我不记得确切的细节,但您可以假设每个类型都有一个vtable。在层次结构的每个级别上,该类型都有自己的vtable和