在钻石问题的求解中,为什么要虚拟地继承两次grand-parent类

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

本文关键字:继承 grand-parent 两次 虚拟 问题 钻石 为什么      更新时间:2023-10-16

在下面的代码中,为什么我必须在类BC中都继承类A

我了解到第一个编译器在派生类中找到目标函数。若并没有在其中找到,那个么编译器必须在它的基类中进行搜索。所以,如果我实际上只继承了一次(类A(,那么编译器应该只通过一个路径找到目标函数(aa()(,即下面的DCA

若我只继承了一次,那个么问题仍然存在。

#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()`
一个答案是:仅仅因为。这是规则。你也有。时期

如果你想要另一条规则,就编另一种语言。如果你不喜欢任何编程语言中的规则,请尝试这样做,然后回复我们,告诉我们你想出了什么确切的语义。

但一个更深层次的答案是,虚拟污染非虚拟的东西毫无意义。

你是否希望一个非虚拟成员函数变成虚拟的,因为其他具有相同签名的函数在其他类中是虚拟的,并且它们最终是两个基?这是一个真实的问题,用一种想象中的语言,你可以根据自己的直觉来弥补:

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的语义,那么你有一个大致的答案:编写虚拟性不会改变任何先前建立的虚拟性的虚拟性。

因此,你所要接受的是,虚性是继承的一种性质,是派生类和它的基之间的一对一关系。继承在任何给定的类中都被建立为虚拟或非虚拟。

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

虚拟基类

对于指定为虚拟的每个不同基类派生对象只包含一个该类型的基类子对象,即使类在继承层次结构中多次出现(只要它每次都是虚拟继承的(。

也许你的理解是"一旦继承了虚拟基类,它只作为子对象出现一次",但更正确的非正式方式是"所有虚拟继承的子对象都只作为单个子对象出现"。

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

struct baseA : base {};

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

struct baseB : virtual base {};

现在我们可以从两者继承:

struct foo : baseA, baseB {};

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

这实际上只是猜测和TL;DR实际上就是:这就是虚拟继承的定义。

包含的链接中有一个很好的解释。

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

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

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

如果删除类C的virtual关键字,则在类树中有第二个a实例。如果你在课堂上有两次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;
}