C ++中由多重继承引起的复制构造函数的不明确调用

问题描述 投票:6回答:3

我在执行某项任务时遇到问题,这是一种锻炼,不是真正的程序。任务是定义结构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,该问题与常见的基类有关具有不同的访问说明,最终是由转换问题引起的。这是关于为具有相同可见性的多次继承的通用库消除所需的初始化程序的歧义。

c++ multiple-inheritance copy-constructor ambiguity ambiguous
3个回答
5
投票

注意:答案基础已编辑!] >>

问题分析:

您正在使用multiple inheritance with a diamond problem

更具体地说,您的结构D继承了相同的基类A类三遍:一次(C0),一次(C的间接)继承。由于基类不是虚拟的,因此D有3个不同的A子对象。C ++ 11标准节10.1 / 4-5

将其称为lattice] ::

“继承图”

[通常,您随后将使用明确的限定条件消除每个struct D: C,A的成员的歧义,并告诉编译器您要引用的是3 A个子对象中的哪个。 C ++ 11第10.1 / 5节

对此进行了说明。成员的语法应为A范围内的A::aC::aB::a,如果您在外面,则每个元素的最终前缀为D

不幸的是,C ++ 11第10.2 / 5-6节]中的成员名称查找逻辑确保了直接D::基始终会使得其他间接A基变得模棱两可,尽管有明确的限定条件(甚至A语句)。

最终解决方案:

由于问题是由直接基类引起的,并且没有办法将此与其他基类进行歧义,所以唯一有效的解决方案是使用空的中间类来强制使用其他名称:

using

此代码编译并与MSVC2013,clang 3.4.1和gcc 4.9一起使用。


其他(非)解决方案:

我以前的回答仅基于明确的限定条件。尽管有很多批评,但我确实在MSVC2013上成功地对其进行了编译和测试!但是,这是一件很奇怪的事情:在编辑器中,智能突出显示了歧义,但是编译运行良好,没有任何错误。我最初以为这是一个智能错误,但是现在意识到这是编译器不合规(错误?)

建议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; } 的答案已编译,但未通过测试。为什么呢因为D(const D& other) : C(other), A((const B)other), d(other.d)A((const B)other)理解为是other。因此,直接用B间接继承的A的值初始化直接在D中的A。这是一个非常令人讨厌的错误,我花了一段时间才注意到。

当然,您可以使用虚拟基类。然后,D中将只有一个B子对象,这解决了许多问题。但是我不知道您要设计的是什么,有些设计需要网格而不是虚拟钻石。

[如果可以负担两步,请执行以下步骤(步骤1:基数的默认初始化;步骤2:在基数上复制目标值),当然也有使用歧义成员函数返回正确基数引用的方法。但这比上面介绍的简单解决方案更棘手,更容易出错。

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

A

进一步编辑:

但是,为了避免锻炼中出现很多歧义性问题,您应该使用A

在您的情况下,从C到A的路径不明确:C-> A或C-> B-> A要将A声明为层次结构中的共同祖先,您需要将B或C到A的继承声明为虚拟。从D到A还有一条模棱两可的路径:D-> A或(D-> C-> A或D-> C-> B-> A)。因此,您还将从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) {
}

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

virtual inheritance

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

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

通过双参数作为参考传递前进是可以的。

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

0
投票

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

A

0
投票

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

通过双参数作为参考传递前进是可以的。

© www.soinside.com 2019 - 2024. All rights reserved.