解决菱形问题,为什么要虚拟继承grand parent class两次?

in solution of diamond problem, why we need to inherit grand parent class two time virtually?

在下面的代码中,为什么我必须在 classes BC 中虚拟地继承 class A

我了解到编译器首先在派生 class 中找到目标函数。如果在其中找不到,则编译器必须在其基数 classes 中进行搜索。因此,如果我实际上只继承一次 (class A),那么编译器应该只通过一条路径找到目标函数 (aa()),即 DC在下面的情况下为 A

如果我只继承一次,那么问题仍然存在。

#include <iostream.h>
using namespace std;


class A
{
public: 
    void aa() { cout<<"aa"<<endl; } 
};

class B: public virtual A
{  };

class C: public /*virtual*/ A
{  };

class D: public C,public B
{  };

int main()
{
    D d1;
    d1.aa();
    return 0;
}

我收到这个错误:

Error : Member is ambiguous: 'A::aa' and 'A::aa' in function `main()`

一个答案是:仅仅因为。这是规则。你也有。期间.

如果你想要另一个规则,请编写另一种语言。如果您不喜欢任何编程语言中的规则并返回给我们,请尝试这样做,告诉我们您提出的确切语义。

但更深层的答案是,虚拟污染非虚拟的东西几乎没有意义。

您是否希望非虚拟成员函数变为虚拟,因为具有相同签名的其他一些函数在其他 class 中是虚拟的,并且它们最终成为两个基数?这是一道真题,您可以根据自己的直觉编造一种虚构的语言:

struct A {
   void f(); // non virtual!
};

struct AA : A {
   void f(); // non virtual, hides, does not override
};

struct B {
   virtual void f();
};

struct BB : B {
   void f(); // virtual overrider 
};

struct A {
   void f(); // non virtual!
};

struct AABB : AA, BB {
   // is AA::f() now virtual? what about A::f()?
};

如果您不希望 BB 中的虚拟性改变 AA 的语义,那么您有一个普遍的答案:编写虚拟性不会改变任何先前建立的虚拟性的虚拟性。

因此,您必须接受的是,虚拟性是一种 属性 继承,是派生 class 与其基础之间的一对一关系。在任何给定的 class.

中,继承被确定为虚拟或非虚拟

这就是它的工作原理。来自 cppreference

Virtual base classes

For each distinct base class that is specified virtual, the most derived object contains only one base class subobject of that type, even if the class appears many times in the inheritance hierarchy (as long as it is inherited virtual every time).

也许你的理解是"once inherited virtual the base class appears only once as subobject",但更正确的非正式方式应该是"all virtual inherited subobjects appear as only a single subobject"。

考虑人为的情况,其中 baseA 希望它的 base 出现在最终派生的 class 中。它将使用非虚拟继承:

struct baseA : base {};

现在 baseB class 希望其派生包含一个 base 子对象,该子对象在所有其他虚拟继承基 class 之间共享,它将使用虚拟继承:

struct baseB : virtual base {};

现在我们可以继承两者:

struct foo : baseA, baseB {};

如果虚拟继承按您预期的方式工作,这将破坏 baseA(假设 foo 获得一个单独的 base 子对象)。这也是违反直觉的,添加 baseB 继承会将 baseA 的继承变成虚拟继承。

这实际上只是猜测,TL;DR 实际上只是:根据定义,这就是虚拟继承的工作方式。

随附的 link 中有很好的解释。

https://isocpp.org/wiki/faq/multiple-inheritance

您需要向下滚动到关于 "the dreaded diamond" 的部分。下面的部分解释了如何处理可怕的钻石——通过使用关键字 "virtual".

简而言之,当您拥有多重继承时,并且您继承的某些事物共享一个共同的祖先,您最终会得到基础对象的两个副本。在您的示例中,您获得了 A 的两份副本——一份在 B 中,一份在 C 中。然后当从 D 中使用它时,您的代码不知道您真正指的是哪一个。那可能不是你真正想要的。您只想要 A 的单个实例,并且希望 B 和 C 共享它。

如果您删除 class C 的 virtual 关键字,您的 class 树中就会有 A 的第二个实例。如果您的 class 中有两次 A 访问成员是 ambiguous!

如果您像这样扩展您的示例,您可以看到两个实例并摆脱错误消息:

int instance_count = 0;
class A
{
    int myInstance;

    public:

    A(): myInstance{ instance_count++ } { std::cout << "create instance # " << myInstance << std::endl;}

    void aa() { std::cout<<"Instance"<< myInstance << std::endl; }
};

class B:public A
{  };
class C:public /*virtual*/ A
{  };
class D:public C,public B
{  };

int main()
{
    D d1;
    d1.::C::aa(); // you can access each instance by giving the path through the hirarchy
    d1.::B::aa();
    return 0;
}