Неоднозначный вызов конструктора копирования в C ++, вызванный множественным наследованием

У меня проблема с определенной задачей, это упражнение, а не настоящая программа. Задача состоит в том, чтобы определить конструктор копирования структуры 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) {}
^

ВАЖНОЕ ЗАМЕЧАНИЕ: Это НЕ ДУБЛИКАТ вопроса «Недоступная прямая база, вызванная множественным наследованием«, которая касалась общих базовых классов с различными спецификаторами доступа и была в конце концов вызвана проблемой преобразования. Здесь речь идет об устранении неоднозначности требуемого инициализатора для общей базы, унаследованной несколько раз с одинаковой видимостью.

6

Решение

ВНИМАНИЕ: ОТВЕТ ФУНДАМЕНТАЛЬНО ИЗМЕНЕНО!

Анализ проблемы:

Ты используешь множественное наследование с проблемой алмаза.

Более конкретно, ваша структура D наследует ту же базу A класс три раза: один раз напрямую (struct D: C,A) и дважды косвенно (через наследование C). Так как базовые классы не являются виртуальными, есть 3 различных подобъекта A для D. C ++ 11 стандартный раздел 10.1 / 4-5 называет это решетка:

схема наследования

Как правило, вы бы двусмысленно члены каждого A с явной квалификацией, сообщающей компилятору, какой из 3 A подобъект, на который вы ссылаетесь. Это объясняется в C ++ 11 раздел 10.1 / 5. Синтаксис для членов должен быть A::a, C::a а также B::a в рамках Dкаждому в конечном итоге предшествует 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& other) : C(other), A((const B)other), d(other.d) компилируется, но не проходит тест. Зачем ? так как A((const B)other) пойму other будучи B, Итак A прямо в D будет инициализирован со значением A косвенно наследуется от B (так другое A). Это очень неприятная ошибка, и мне потребовалось некоторое время, чтобы заметить.

Конечно, вы можете использовать виртуальные базовые классы. Тогда будет только один A подобъект в D, который решает много проблем. Однако я не знаю, что вы разрабатываете, и некоторые проекты требуют решетки, а не виртуального алмаза.

Если бы вы могли позволить себе двухэтапное копирование (шаг 1: инициализация базы по умолчанию; шаг 2: копирование целевого значения на базу), безусловно, существуют подходы, использующие функции-члены с разноименными точками, возвращающие ссылку на правильную базу. Но это может быть более сложным и подверженным ошибкам, чем простое решение, представленное выше.

5

Другие решения

Конструктор копирования, сгенерированный компилятором, вызовет конструктор по умолчанию базового класса деда. Это, вероятно, не то, что вы хотите.
Чтобы иметь чистый дизайн с вызовом только конструкторов копирования, вы должны реализовать конструктор копирования каждого класса и убедиться, что они вызываются так, как вам нужно. В вашем текущем примере иерархии вы не можете привести непосредственно из 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) {
}

Дальнейшее редактирование:

Но для начала, и чтобы избежать многих проблем с неопределенностью в вашем упражнении, вы должны были использовать виртуальное наследство.

В вашем случае существует неоднозначный путь от 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 copy, как вы хотели:

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

Член D :: A :: a будет таким же, как D :: C :: A :: a или D :: C :: B :: A :: a.

0

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector