Как компилятор решает проблему алмаза на С++?

Мы знаем, что мы можем решить задачу алмаза, используя виртуальное наследование.

Например:

   class Animal // base class
   {
     int weight;
     public:
     int getWeight() { return weight;};
   };
   class Tiger : public Animal { /* ... */ }; 
   class Lion : public Animal { /* ... */ };
   class Liger : public Tiger, public Lion { /* ... */ }; 
   int main()
   {
     Liger lg ;
     /*COMPILE ERROR, the code below will not get past
     any C++ compiler */
     int weight = lg.getWeight();
   }

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

Ответ 1

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

Когда ему нужно найти переменную-член (вес в вашем примере), компилятор начинается с фактического класса, в вашем случае Liger. Он не найдет там элемент веса, поэтому он перемещается на один уровень до родительского класса (ов). В этом случае их два, поэтому он сканирует как Tiger, так и Lion для члена веса имени. Пока еще нет хитов, поэтому теперь нужно подняться на еще один уровень, но нужно сделать это дважды, один раз для каждого класса на этом уровне. Это продолжается до тех пор, пока требуемый элемент не будет найден на некотором уровне дерева наследования. Если на каком-либо заданном уровне он находит только один член, рассматривающий все ветки множественного наследования, все хорошо, если он находит двух или более членов с требуемым именем, тогда он не может решить, какой из них выбрать, чтобы он делал ошибки.

Ответ 2

Когда компилятор создает таблицу указателей функций для класса, каждый символ должен появляться в нем ровно один раз. В этом примере getWeight появляется дважды: в Tiger и в Lion (потому что Liger не реализует его, поэтому он поднимается вверх по дереву, чтобы его искать), поэтому компилятор застревает.

Это довольно просто, на самом деле.

Ответ 3

Компилятор ищет getWeight в Liger, не находит никого, а затем проверяет его родителей и родительских родителей и т.д. и т.д., если он находит больше одного, он возвращает ошибку и умирает от вас, потому что он не может сказать который он должен использовать.

Ответ 4

С вашим кодом структура для liger

Liger[Tiger[Animal]Lion[Animal]]

Если вы вызываете функцию Animal из указателя Liger, на самом деле есть два объекта Animal a Liger, которые могут преобразовать (следовательно, двусмысленность)

Виртуальное наследование будет генерировать структуру типа

Liger[Tiger[*]Lion[Animal]]
            \-----/

В настоящее время существует только один Animal, косвенно достижимый с обеих баз, поэтому преобразование из Liger в Animal больше двусмысленно.