虚拟调度实施细节

Arm*_*yan 31 c++ vtable vptr

首先,我想清楚地表明我确实理解在C++标准中没有vtable和vptrs的概念.但是我认为几乎所有实现都以几乎相同的方式实现虚拟调度机制(如果我错了,请纠正我,但这不是主要问题).另外,我相信我知道虚函数如何工作的,也就是说,我总能告诉我将调用哪个函数,我只需要实现细节.

假设有人问我以下内容:
"您的基类B具有虚函数v1,v2,v3和派生类D:B,它会覆盖函数v1和v3并添加虚函数v4.解释虚拟调度的工作原理".

我会这样回答:
对于每个具有虚函数的类(在本例中为B和D),我们有一个单独的指向函数的数组,称为vtable.
B的vtable将包含

&B::v1
&B::v2
&B::v3
Run Code Online (Sandbox Code Playgroud)

D的vtable将包含

&D::v1
&B::v2
&D::v3
&D::v4 
Run Code Online (Sandbox Code Playgroud)

现在B类包含一个成员指针vptr.D自然地继承它,因此也包含它.在BB的构造函数和析构函数中设置vptr指向B的vtable.在DD的构​​造函数和析构函数中,它指向D的vtable.
对多态类X的对象x上的虚函数f的任何调用都被解释为对x.vptr的调用[f在vtables中的位置]

问题是:
1.我在上述描述中有任何错误吗?
2.编译器如何知道f在vtable中的位置(请详细说明)
3.这是否意味着如果一个类有两个基数那么它有两个vpt?在这种情况下发生了什么?(尝试以与我相似的方式描述,尽可能详细地描述)
4.钻石层次结构中发生了什么,其中A位于顶部B,C位于中间,D位于底部?(A是B和C的虚拟基类)

提前致谢.

Tra*_*kel 35

1.我在上面的描述中有任何错误吗?

都好.:-)

2.编译器如何知道f在vtable中的位置

每个供应商都有自己的方法,但我总是把vtable视为成员函数签名到内存偏移的映射.所以编译器只维护这个列表.

这是否意味着如果一个班级有两个基地,那么它有两个vptrs?在这种情况下发生了什么?

通常,编译器组成一个新的 vtable,它包含按指定顺序附加在一起的虚拟碱基的所有vtable,以及虚拟碱基的vtable指针.它们遵循派生类的vtable函数.这是极其供应商特定的,但对于class D : B1, B2你通常会看到D._vptr[0] == B1._vptr.

多重继承

该图像实际上是用于组成对象的成员字段,但vtable可以由编译器以完全相同的方式组成(据我所知).

4.钻石层次结构中发生了什么,A在顶部B,C在中间,D在底部?(A是B和C的虚拟基类)

简短的回答?绝对的地狱.你真的继承了这两个基地吗?只是其中之一?他们都不是?最终,使用了为该类组成vtable的相同技术,但是这样做是如何完成的,因为它应该如何完成并不是一成不变的.有解决的钻石层次问题的解释体面这里,但是,像极了这一点,这是很特定于供应商.

  • @Armen,@ Oli:我在[codepad](http://codepad.org/w9AzO5c0)中写了一个小片段.这是我的第一篇文章,所以如果它不起作用告诉我,我将在其他地方生成代码. (2认同)

Lou*_*nco 5

  1. 在我看来很好
  2. 具体实现,但大多数只是源代码顺序 - 意味着它们出现在类中的顺序 - 从基类开始,然后从派生类添加新的虚函数.只要编译器具有确定性的方法,那么它想做的任何事情都可以.但是,在Windows上,要创建COM兼容的V-Tables,它必须按源顺序排列

  3. (不确定)

  4. (猜测)钻石只意味着您可以拥有基类B的两个副本.虚拟继承将它们合并到一个实例中.因此,如果您通过D1设置成员,则可以通过D2读取它.(C衍生自D1,D2,它们各自衍生自B).我相信在这两种情况下,vtable都是相同的,因为函数指针是相同的 - 数据成员的内存是合并的.