【问题标题】:in solution of diamond problem, why we need to inherit grand parent class two time virtually?在解决钻石问题时,为什么我们需要虚拟继承两次祖父类?
【发布时间】:2019-10-29 19:07:31
【问题描述】:

在下面的代码中,为什么我必须在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()`

【问题讨论】:

  • 您为什么认为它会起作用?你能说出你的直觉吗?
  • ... 编译器必须在它的 all 基类中搜索 我强调 all 因为它意味着 所有 基类。这就是编译器找到两个匹配项的时候。
  • @dr-gut 我在编辑后合并了您的编辑(语法修复)(我的编辑在语法上较差)
  • 实际上有两个继承路径:D-C-A 和 D-B-A 所以两个 A 实例和两组实例成员。

标签: c++ multiple-inheritance virtual-inheritance


【解决方案1】:

一个答案是:只是因为。这是规矩。你也有。期间。

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

    虚拟基类

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

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

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

    struct baseA : base {};
    

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

    struct baseB : virtual base {};
    

    现在我们可以继承两者:

    struct foo : baseA, baseB {};
    

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

    这实际上只是推测,而 TL;DR 真的只是:虚拟继承的定义就是这样。

    【讨论】:

      【解决方案3】:

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

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

      您需要向下滚动到“可怕的钻石”部分的一半。下面的部分解释了如何处理可怕的钻石——通过使用关键字“虚拟”。

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

      【讨论】:

        【解决方案4】:

        如果您删除类 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;
        }
        

        【讨论】:

          猜你喜欢
          • 2017-12-09
          • 1970-01-01
          • 1970-01-01
          • 2020-01-01
          • 2012-11-05
          • 2011-02-09
          • 2011-11-18
          相关资源
          最近更新 更多