Множественное наследование без виртуальных функций в С++

Я столкнулся с проблемой бриллианта и нашел разные решения для разных случаев с одним бриллиантом. Однако я не смог найти решение для «цепных» бриллиантов.

Согласно структуре: да, я хочу каждый раз иметь несколько базовых классов, поэтому виртуальное наследование не является решением (тогда оно вообще называется бриллиантом?). Я также хотел избежать функций get/set для каждого среднего слоя алмаза.

p   p
|   |
k   k
 \ /
  s

class parent { int val; };
class kid1 : public parent {};
class kid2 : public parent {};
class school : public kid1, public kid2 {};

Доступ к val в родительском классе теперь работает следующим образом:

school* s = new school;
s->kid1::val=1; // works

Но как насчет следующего «цепного» бриллианта:

p   p   p   p
|   |   |   |
k   k   k   k
 \ /     \ /
  s       s
  |       |
  c       c
    \   /
      w

class country1 : public school {};
class country2 : public school {};
class world : public country1, public country2 {};

Доступ к val через:

world* w = new world;
w->country1::kid1::val=1; // error

приводит к:

error: ‘kid1’ is an ambiguous base of ‘world’

Почему? Разве путь к значению не определен?


person nem    schedule 18.03.2011    source источник
comment
@Nikolay: Я думаю, @nem это знает. Но для каждой страны у нас есть однозначная база kid1. Отсюда вопрос, почему это не работает   -  person Armen Tsirunyan    schedule 18.03.2011
comment
Я почти уверен, что отношение school : kid нарушает LSP. По крайней мере, я никогда не был в школе, где было двое детей :)   -  person fredoverflow    schedule 18.03.2011
comment
Я опасаюсь за здравомыслие того, кто в конечном итоге будет поддерживать эту кодовую базу — она даже свела с ума бедную Visual Studio. IDE регистрирует это как ошибку, а компилятор — нет.   -  person Jon    schedule 18.03.2011
comment
это было просто гипотетически :) у меня другая реализация.   -  person nem    schedule 18.03.2011
comment
@Fred, @Jon: согласен, здесь лучше использовать композицию, чем наследование   -  person Ben Voigt    schedule 18.03.2011


Ответы (2)


s->kid1::val не означает "val из подобъекта kid1". Это просто имя, определяемое типом (а не подобъектом), который его содержит.

Я не знаю, почему country1::kid1 вообще принимается, но, видимо, это typedef для ::kid1. Два члена данных в world имеют полное имя ::kid1::val.

Что вы хотите:

world* w = new world;
country1* const c1 = world;
c1->kid1::val = 1;
person Ben Voigt    schedule 18.03.2011

Это. Ошибка связана с ошибкой в ​​вашем компиляторе.

person James Kanze    schedule 18.03.2011
comment
Учитывая, что я уже объяснил, почему код неверен, любое заявление о том, что код правильный, должно сопровождаться ссылкой на конкретную часть стандарта С++, которая, по вашему мнению, это допускает :) - person Ben Voigt; 18.03.2011
comment
§3.4.3 Поиск квалифицированного имени вполне ясен. Учитывая w-›country1::kid1::val, компилятор использует доступ к члену класса §3.4.5 для поиска страны1; согласно §3.4.5/4, если id-выражение является квалифицированным id (это так), этот поиск происходит как в области класса, так и в контексте полного выражения, поэтому компилятор находит ::country1. После этого применяются обычные правила из §3.4.3. - person James Kanze; 18.03.2011
comment
@James: И затем правило применяется снова (есть два оператора разрешения области), и компилятор находит ::kid1 (как я сказал в своем ответе). Но ::kid1::val неоднозначно, компилятор прав. Нет country1::kid1, кроме ::kid1, операторы разрешения области работают с именами типов, а не с именами подобъектов (и базовые подобъекты не имеют имен, поэтому вы не можете сказать w->country1.kid1.val). - person Ben Voigt; 18.03.2011
comment
@ Бен Фойгт Какое правило? После того, как компилятор нашел страну1, он выполняет поиск по квалифицированному имени (§3.4.3) и не ищет (или не должен) нигде, кроме страны1. - person James Kanze; 18.03.2011
comment
@Джеймс: Что такое w->country1::kid1? Тип? Тогда w->country1::kid1::val неоднозначен, потому что этот тип дважды является базовым классом world. Подобъект? Тогда незаконно следовать за ним с ::. - person Ben Voigt; 18.03.2011
comment
Хороший вопрос:-). Это явно тип (поскольку независимо от того, как вы его ищете, kid1 — это класс). С другой стороны, подобные вещи всегда работали в C++, со времен CFront; если сегодня не работает, то, вероятно, проблема в формулировке стандарта. - person James Kanze; 21.03.2011
comment
@James: я был бы уверен, если бы вы могли предоставить пример кода, который компилируется под Comeau. Но я думаю, что этот код действительно неоднозначен. - person Ben Voigt; 21.03.2011
comment
@Ben Voigt Мой опыт с этим датируется немного раньше Комо; более ранние компиляторы обрабатывали это так, как я описал. Но в последнее время я не пробовал; по мере того, как мои навыки C++ улучшались, я обнаружил, что проекты, требующие этого, как правило, были не очень хорошими. - person James Kanze; 23.03.2011
comment
@BenVoigt тот же код, что и OP, полностью компилируется в VS2012. кроме того, country1::kid1 совсем не двусмыслен, поскольку, когда вы находитесь в области действия некоторых классов, доступны все его родители или прародители. это означает, что kid1 должен быть доступен в области country1. - person Ali1S232; 16.12.2012
comment
@Gajoo: известно, что компилятор Microsoft C++ не очень точно следует стандарту. Имя типа kid имеется, разумеется. Но есть два подобъекта этого типа, отсюда и двусмысленность. - person Ben Voigt; 17.12.2012
comment
@BenVoigt есть только один класс с именем kid1, никто ничего не говорит о имени типа kid, поскольку его вообще нет. и, конечно же, в мировом классе есть два подобъекта kid1, но country1::kid1 уникален. - person Ali1S232; 17.12.2012
comment
@Gajoo: country1::kid1 не уникален. kid1, country1::kid1 и country2::kid1 — это имена одного и того же типа. И да, я имел в виду kid1, а не kid в предыдущем комментарии. - person Ben Voigt; 17.12.2012