C++ 在钻石问题的解决方案中,为什么我们需要虚拟地两次继承父类?

C++ 在钻石问题的解决方案中,为什么我们需要虚拟地两次继承父类?,c++,multiple-inheritance,virtual-inheritance,C++,Multiple Inheritance,Virtual Inheritance,在下面的代码中,为什么我必须在两个类B和C中继承类A 我了解到,第一个编译器在派生类中找到目标函数。若并没有在其中找到它,那个么编译器必须在它的基类中进行搜索。所以,如果我实际上只继承一次(类A),那么编译器应该只通过一条路径找到目标函数(aa()),即在下面的情况下从D到C到A) 若我只继承一次,那个么问题仍然存在 #include <iostream.h> using namespace std; class A { public: void aa() { cout

在下面的代码中,为什么我必须在两个类
B
C
中继承类
A

我了解到,第一个编译器在派生类中找到目标函数。若并没有在其中找到它,那个么编译器必须在它的基类中进行搜索。所以,如果我实际上只继承一次(类
A
),那么编译器应该只通过一条路径找到目标函数(
aa()
),即在下面的情况下从
D
C
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;
}

一个答案是:仅仅因为。这是规定。你也有。句号

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

但更深层次的答案是,虚拟环境污染非虚拟环境是毫无意义的

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

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
的语义,那么您得到了一般性的答案:编写虚拟性不会改变任何先前建立的虚拟性的虚拟性


因此,您必须接受的是,虚拟性是继承的属性,是派生类与其基之间的一对一关系。继承在任何给定的类中都被建立为虚拟的或非虚拟的。

这就是它的工作原理。发件人:

虚拟基类

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

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

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

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

struct baseA : base {};
struct baseB : virtual base {};
现在我们可以从这两个方面继承:

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


这其实都只是猜测和猜测;DR实际上只是:根据定义,虚拟继承就是这样工作的。

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

你需要向下滚动到“可怕的钻石”部分。下面这一节解释了如何通过使用关键词“虚拟”来处理可怕的钻石


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

如果删除C类的
virtual
关键字,则在类树中有a的第二个实例。如果你有两次在你的类中访问一个成员是不明确的

如果像这样扩展示例,则可以同时看到这两个实例并消除错误消息:

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;
}
int实例计数=0;
甲级
{
int-myInstance;
公众:

A():myInstance{instance_count++}{std::cout为什么你认为它会起作用?你能说出你的直觉吗?…编译器必须搜索我强调的所有基类都是因为它意味着所有基类。这时编译器会找到两个匹配项。@gut博士,我编辑了你的编辑(语法修复)后将其合并了(我的编辑在语法上是低级的)实际上有两个继承路径:D-C-A和D-B-A,所以A的两个实例和两组实例成员。