У меня проблема с определенной задачей, это упражнение, а не настоящая программа. Задача состоит в том, чтобы определить конструктор копирования структуры 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) {}
^
ВАЖНОЕ ЗАМЕЧАНИЕ: Это НЕ ДУБЛИКАТ вопроса «Недоступная прямая база, вызванная множественным наследованием«, которая касалась общих базовых классов с различными спецификаторами доступа и была в конце концов вызвана проблемой преобразования. Здесь речь идет об устранении неоднозначности требуемого инициализатора для общей базы, унаследованной несколько раз с одинаковой видимостью.
ВНИМАНИЕ: ОТВЕТ ФУНДАМЕНТАЛЬНО ИЗМЕНЕНО!
Анализ проблемы:
Ты используешь множественное наследование с проблемой алмаза.
Более конкретно, ваша структура 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: копирование целевого значения на базу), безусловно, существуют подходы, использующие функции-члены с разноименными точками, возвращающие ссылку на правильную базу. Но это может быть более сложным и подверженным ошибкам, чем простое решение, представленное выше.
Конструктор копирования, сгенерированный компилятором, вызовет конструктор по умолчанию базового класса деда. Это, вероятно, не то, что вы хотите.
Чтобы иметь чистый дизайн с вызовом только конструкторов копирования, вы должны реализовать конструктор копирования каждого класса и убедиться, что они вызываются так, как вам нужно. В вашем текущем примере иерархии вы не можете привести непосредственно из 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.