3

在尝试更深入地分析 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++ 的哪个规范细节处理这种情况以及它是如何从低级别的角度来处理的?

4

1 回答 1

4

可怕的钻石中有一个单一的基础,两个中间对象从该基础派生,然后第四种类型关闭了钻石,并从中间级别的两种类型中获得了多重继承。

您的问题似乎是在前面的示例中声明了多少个函数?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::fasLeft不会覆盖该函数。

当菱形关闭时,因为继承是virtual有一个Base对象,它声明了一个f函数。在第二级继承中,Right用自己的实现覆盖该函数,这是最派生类型的最终覆盖器Bottom

您可能想在标准之外查看它,并看看编译器实际上是如何实现的。编译器在创建对象时会添加一个指向虚拟表Base的隐藏指针。vptr虚拟表包含指向thunk的指针(为简单起见,假设该表包含指向函数最终覆盖器的指针,[1])。在这种情况下,该Base对象将不包含任何成员数据,而只是一个指向包含指向函数指针的表的指针Base::f

Leftextends时Base,一个新的 vtable 被创建,Left并且该 vtable 中的指针被设置为f这个级别的最终覆盖,这是顺便Base::f说一下,两个 vtable 中的指针(忽略蹦床)跳转到相同的实际实现。Left在构造类型对象时,Base首先初始化子对象,然后在初始化Left(如果有的话)的成员之前Base::vptr更新指针以引用Left::vtable(即存储在中的指针Base引用为 定义的表Left)。

在菱形的另一侧,为其创建的 vtable包含Right一个最终调用. 如果要创建一个类型的对象,则会发生相同的初始化过程,并且将指向.Right::fRightBase::vptrDerived::f

现在我们得到最终的对象Bottom。同样,为该类型生成一个 vtable,Bottom并且该 vtable 与所有其他情况一样,包含一个表示f. 编译器分析继承的层次结构,确定Right::foverrides Base::f,左分支上没有等价的override,所以在Bottom's vtable中代表的指针指向fRight::f。同样,在Bottom对象的构造过程中,Base::vptr更新以引用Bottom的 vtable。

如您所见,所有四个 vtable 都有一个条目f,程序中只有一个 f条目,即使每个 vtable 中存储的值不同(最终覆盖器不同)。

[1] thunk是一小段代码,它this在需要时调整指针(多重继承通常意味着它是需要的),然后将调用转发给实际的覆盖。在单继承的情况下,this不需要更新指针,thunk消失,vtable 中的条目直接指向实际函数。

于 2012-04-15T23:10:54.713 回答