【问题标题】:Is this function call really ambiguous?这个函数调用真的模棱两可吗?
【发布时间】:2017-05-15 16:57:54
【问题描述】:

我正在学习多重继承和菱形问题,当我从最派生的类中调用函数时,Visual Studio 告诉我调用不明确:

struct A
{
    virtual void aFunction() { cout << "I am A\n"; }
};
struct B : A {};
struct C : A {};
struct D : B, C {};

int main()
{
   D DObj;
   DObj.aFunction();             //  This is an ambiguous call
}

我知道如果我在 B 和 C 类中重写了基类函数,那么调用将是不明确的,但 B 和 C 中的“aFunction()”不一样吗?

此外,使 B 和 C 从 A 继承实际上会使错误消失。但是我对继承时的关键字“virtual”的理解,即(Derived:virtual Base),它可以防止更下游的“更多派生类”继承链上Base的多个副本。在继承中,可以继承多个成员变量副本,但只能继承同名函数的一个副本。因此,例如,我可以有 5 个派生类,每个派生自 Base,然后是从所有 5 个派生类继承的 MostDerivedClass,在 MostDerivedClass 中,我将拥有 Base 类“成员变量”的 5 个副本,但只有一个函数的副本同名。

换句话说,继承的“virtual”关键字应该防止多个Base“成员变量”副本。我不明白为什么在这种情况下它会清除一个模棱两可的函数调用。

编辑:谢谢,它正在慢慢下沉。我无法想象 D 中 A 的“两个副本”,因为 A 是空的(没有大小)。但后来我记得 C++ 从不创建空类,例如在我的设置中,一个空类的大小为 1。然后我能够想象 A 在 D 中的“两个副本”,现在它开始变得有意义了。

【问题讨论】:

  • 我认为没有虚拟你有两个相同的功能副本(尽管这仍然是我必须自己研究的东西)
  • 是的,没错。 D 包含 A 的两个实例。编译器不知道您是要调用 D::B::A::aFunction 还是 D::C::A::aFunction。尽管在任何一种情况下调用的函数都是相同的,但不同的调用会得到不同的this 指针。如果您从 A 虚拟派生,那么您将只有一个 A 并且没有歧义。 struct B : virtual A {};struct C : virtual A {};.
  • 而且(只是为了证明这不是 Visual C++ 编译器的问题),GCC 5.1 给出了the same error
  • 这个函数调用真的模棱两可吗? -- 如果世界各地成千上万使用的编译器在使用您的示例代码时遇到如此简单的错误,您就会知道关于它,相信我。
  • 你上面有所谓的可怕的钻石 Here is a good tidbit

标签: c++ inheritance polymorphism virtual


【解决方案1】:

调用不明确,因为有两个可能的A 基础对象可以作为调用的this 参数传递。尽管最终调用的是同一个物理函数,并且该函数完全忽略了它的 this 参数,但它们中有两个的事实使其模棱两可。

使用virtual 继承意味着只有一个A 基对象,因此调用不会有歧义。

【讨论】:

    【解决方案2】:

    由于继承了多个成员变量副本,因此您可以拥有具有不同行为的函数的两个单独副本。

    struct A
    {
        int x;
        virtual void aFunction() { cout << "I am A with value " << x ; }
    };
    struct B : A {
    };
    struct C : A {
    };
    struct D : B, C {};
    
    int main()
    {
       D DObj;
       ((B*)(&DObj))->x = 0; // set the x in B to 0
       ((C*)(&DObj))->x = 1; // set the x in C to 1
       DObj.aFunction();             //  This is an ambiguous call
    }
    

    这个输出应该是 0 还是 1?

    编译器可以检测到不引用它的内联函数的特定情况,但您可以轻松解决该问题,因此对于相对罕见的情况而言,它不值得复杂。

    【讨论】:

    • 在您的示例中,如果 D 最后调用 B 的构造函数或 A 的构造函数,这无关紧要吗?这很令人困惑,但是如果在构造 D 时它调用 B 的构造函数,然后调用 C 的构造函数,那么答案应该是 1。如果它以另一种方式调用它们,则为 0?
    • 重点是x成员变量有两个副本,那么aFunction应该引用哪个版本呢?
    • @TitoneMaurice 如果您没有虚拟继承 A,则 A 有两个副本,一个在 B 中,一个在 C 中,因此您有两个 x 副本。通过 D 访问时,访问哪个版本的 x 是不明确的。
    • @user1937197 啊,是的,谢谢,我现在明白了。 x 成员有两个副本。但是在我的示例中,基类中没有成员变量,因此如果有两个 Base 副本,就很难准确地看到正在发生的事情。我还是不明白。
    • @TitoneMaurice 编译器将其视为与成员完全相同,因此有两个潜在的 this 值可以传递。
    【解决方案3】:

    使用虚继承解决菱形问题:

    struct A
    {
        int x;
        virtual void aFunction() { cout << "I am A with value " << x ; }
    };
    struct B : virtual A { // add virtual
    };
    struct C : virtual A { // virtual
    };
    struct D : B, C {};
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-06
      • 2015-03-16
      • 1970-01-01
      相关资源
      最近更新 更多