【问题标题】:Ambiguous call of a copy constructor in C++ caused by multiple inheritance多重继承导致的C++中复制构造函数的不明确调用
【发布时间】:2014-11-09 19:16:10
【问题描述】:

我在某项任务上遇到问题,这是一项练习,而不是真正的程序。任务是定义结构 D 的复制构造函数,其行为方式与编译器生成的复制构造函数完全相同。

class Ob{
};

struct A {
Ob a;
};

struct B : A {
Ob b;
};

struct C : A, B {
Ob c;
};

struct D : C, A {
Ob d;
};

如您所见,结构 A 在 D 结构中间接派生了几次,这导致复制构造函数的定义出现歧义,如下所示:

D(const D& _d) : C(_d), A(_d), d(_d.d) {}

我的问题是如何正确定义该复制构造函数?没有上面提到的定义的代码是编译的,所以看起来应该是可以的。

MinGW 4.8.1 错误信息:

zad3.cpp:12:8: warning: direct base 'A' inaccessible in 'C' due to ambiguity     [enabled by default]
 struct C : A, B {
        ^
zad3.cpp:16:8: warning: direct base 'A' inaccessible in 'D' due to ambiguity     [enabled by default]
 struct D : C, A {
    ^
zad3.cpp: In copy constructor 'D::D(const D&)':
zad3.cpp:17:38: error: 'A' is an ambiguous base of 'D'
 D(const D& _d) : C(_d), A(_d), d(_d.d) {}
                                      ^

重要提示:这是NOT A DUPLICATE问题“Inaccessible direct base由多重继承引起的”,这是关于具有不同访问说明符的公共基类,最终是由转换问题引起的。在这里,它是关于消除具有相同可见性多次继承的公共基础所需的初始化程序的歧义。

【问题讨论】:

  • 你能添加完整的编译器错误信息吗?
  • 哦,我想明白了,yee 编译器错误。
  • @rupesh.yadav:在您尝试直接使用这些成员之前不会出错,例如D::method() { a = A(); }D d = D(); d.a = A();
  • 没有办法。这就是警告告诉你的。请参阅Johannes's answer,了解有关直接基础的独特之处的解释。

标签: c++ multiple-inheritance copy-constructor ambiguity ambiguous


【解决方案1】:

注意:答案已基本编辑!

问题分析:

您正在使用multiple inheritance with a diamond problem

更具体地说,您的结构D 三次继承相同的基类A:一次直接(struct D: C,A)和两次间接(通过C 的继承)。由于基类不是虚拟的,因此 D 有 3 个不同的 A 子对象。C++11 标准第 10.1/4-5 节将此称为 lattice

通常,您会用明确的限定条件来消除每个 A 的成员的歧义,告诉编译器您指的是 3 个 A 子对象中的哪一个。这在 C++11 第 10.1/5 节 中有解释。成员的语法应该是D 范围内的A::aC::aB::a,如果您在外面,则每个最后都以D:: 开头。

不幸的是,C++11 第 10.2/5-6 节 中的成员名称查找逻辑确保直接的 A 基总是会使其他间接的 A 基不明确,尽管显式资格(甚至using 声明)。

最终解决方案:

由于问题是由直接基类引起的,并且没有办法将这个与其他基类区分开来,唯一真正可行的解决方案是使用空的中间类来强制使用不同的名称:

struct Ob{ int v; };    // v aded here to allow verification of copy of all members
struct A { Ob a; };
struct B : A {  Ob b; };
struct A1 : A {};       // intermediary class just for diambiguation of A in C
struct C : A1, B { Ob c; };  // use A1 instead of A
struct A2 : A { };      // intermediary class just for diambiguation of A in D
struct D : C, A2 {        // use A2 instead of A
    Ob d;
    D() { }
    D(const D& _d) : C(_d), A2(_d), d(_d.d) { }
};

int main(int ac, char**av)
{
    cout << "Multiple inheritance\n";
    D x;
    x.A2::a.v = 1;  // without A2:: it's ambiguous
    x.A1::a.v = 2;  // without A1:: it's ambiguous
    x.B::a.v = 3;
    x.b.v = 4;
    x.d.v = 5;

    D y = x;
    cout << "The moment of truth: if not 1 2 3 4 5, there's a problem!\n";
    cout << y.A2::a.v << endl;
    cout << y.A1::a.v << endl;
    cout << y.B::a.v << endl;
    cout << y.b.v << endl;
    cout << y.d.v << endl;
}

此代码编译并使用 MSVC2013、clang 3.4.1 和 gcc 4.9。


其他(非)解决方案:

我之前的回答仅基于明确的限定。尽管有很多批评,但我确实在 MSVC2013 上成功编译和测试了它!然而,它们是一件奇怪的事情:在编辑器中,智能突出了一个模棱两可的地方,但编译运行良好,没有任何错误。我最初认为这是一个智能错误,但现在意识到这是一个编译器不合规性(错误?)

建议D(const D&amp; other) : C(other), A((const B)other), d(other.d) 的答案可以编译,但没有通过测试。为什么 ?因为A((const B)other) 会将other 理解为B。所以直接在D 中的A 将被初始化为间接继承自BA 的值(所以另一个A)。这是一个非常讨厌的错误,我花了一段时间才注意到。

当然你可以使用虚拟基类。那么D中就只有一个A子对象,解决了很多问题。但是我不知道您在设计什么,有些设计需要晶格而不是虚拟菱形。

如果你能负担得起两步复制(第 1 步:基的默认初始化;第 2 步:在基上复制目标值),肯定有使用二歧成员函数返回正确基的引用的方法。但这可能比上面介绍的简单解决方案更棘手且更容易出错。

【讨论】:

  • 我已经尝试过了,这对我来说似乎是合理的,但不幸的是它在 MinGW 4.8.1 和 Microsoft Visual C++ 2013 上都不能编译。两个编译器都会产生类似的错误消息:'A' 是'D' 的模棱两可的基础,并且该类 'A' 已经被初始化
  • 建议的解决方案无法编译。应该使用_d 的哪个A 子对象来初始化基类是不明确的。
  • 我在 MSVC 2013 中编译了上面的所有代码。但请注意:智能感知突出显示了一个歧义错误,代码生成不会报告该错误。并且编译后的代码可以完美运行(因为它是有效的构造,所以它应该如此)。
  • @borrisso 很好,按照您的评论,我花了一夜时间浏览标准,发现我的解决方案之所以有效,只是因为编译器允许(错误?)。现在我有了另一个适用于所有编译器的解决方案。
【解决方案2】:

编译器生成的复制构造函数会调用祖父基类的默认构造函数。这可能不是你想要的。 要实现只调用复制构造函数的简洁设计,您应该实现每个类的复制构造函数,并确保根据需要调用它们。在您当前的层次结构示例中,您不能直接从 D 转换为 A。

A(const A& other), a(other.a) { 
}
//...
B(const B& other) : A(other), b(other.b) { 
}
//...
C(const C& other) : B(other), A((const B)other), c(other.c) {
}
//...
D(const D& other) : C(other), A((const B)other), d(other.d) {
}

进一步编辑:

但首先,为了避免练习中的很多歧义问题,您应该使用virtual inheritance

在您的情况下,从 C 到 A 的路径不明确:C->A 或 C->B->A 要将 A 声明为层次结构中的共同祖先,您将从 B 或 C 到 A 的继承声明为虚拟的。 从 D 到 A 也有一条不明确的路径:要么 D->A,要么(D->C->A,或 D->C->B->A)。因此,您还将从 D 到 A 的继承声明为虚拟的。

struct B : public virtual A { ... }
struct C : public virtual A, public B { ... }
struct D : public C, public virtual A { ... }

现在您的层次结构中只有一个 A 的共享实例。然后,您可以根据需要编写 D 复制构造函数:

D(const D& other) : C(other), A(other), d(other.d) 

成员 D::A::a 将与 D::C::A::a 或 D::C::B::A::a 相同。

【讨论】:

  • 是的,它可以编译。 但是我们这里有多重继承没有虚基。在此复制构造函数中,您只需定义如何复制 B 继承的 A 基址。它不会复制 D 直接继承的 A 基数,也不会复制 C 继承的 A 基数!这两个用他们的默认构造函数初始化!!!
  • 这根本不等同于默认的复制构造函数。
  • 是的,我的第一个答案不准确。我已经完成了。
  • 您是否可以使用我在答案中以“如果您添加成员int v;”开头的验证码来测试您提出的解决方案?我已经尝试过您的解决方案,似乎基类 A(D 的直接基类)之一是从错误的基类 A(B 的基类)复制而来的。你能检查一下吗?
  • 我在 C++11 模式下使用 clang++ 3.4 版,它阻止我按照您的建议将 D 对象直接转换为 A 对象。报告的错误是:从派生类“D”到基类“A”的模糊转换。
【解决方案3】:

请通过引用传递双重参数。

使用作为参考传递的双参数可以进行正向传递。


// The time series function which is t ^ 5
auto func(dual *t) {
    return autodiff::forward::pow(*t, 5.0);
}
// gradient calculation which is at (t)
var func_gradient(dual *t) {
    auto u = func(t);  // the output variable u
    auto ux = autodiff::forward::derivative(u, autodiff::forward::wrt(t), at(t)); // evaluate the derivative of u with respect to x
    return (var) ux;
}

【讨论】:

    猜你喜欢
    • 2012-09-20
    • 2012-05-26
    • 2019-01-12
    • 2014-08-21
    • 2012-02-28
    • 1970-01-01
    • 2018-10-10
    • 1970-01-01
    • 2014-02-17
    相关资源
    最近更新 更多