Почему есть двусмысленность в этом алмазном рисунке?

StackOverflow https://stackoverflow.com/questions/2658556

  •  27-09-2019
  •  | 
  •  

Вопрос

#include <iostream>
using namespace std;

class A             { public: void eat(){ cout<<"A";} };
class B: public A   { public: void eat(){ cout<<"B";} };
class C: public A   { public: void eat(){ cout<<"C";} };
class D: public B,C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = new D();
    a->eat();
}

Я не уверен, что это называется алмазной проблемой или нет, но почему эта работа не эта работа?

Я дал распределение для eat() за D. Отказ Итак, ему не нужно использовать Bили CКопия (Итак, не должно быть проблем).

Когда я сказал, a->eat() (Помните eat() не виртуально), есть только один возможный eat() позвонить, что из A.

Почему тогда я получаю эту ошибку:

«А» - неоднозначная база «D»


Что именно делает A *a = new D(); значит для компилятора ??

и

Почему та же проблема не возникает, когда я использую D *d = new D();?

Это было полезно?

Решение

Представьте себе немного другой сценарий

class A             { protected: int a; public: void eat(){ a++; cout<<a;} };
class B: public A   { public: void eat(){ cout<<a;} };
class C: public A   { public: void eat(){ cout<<a;} };
class D: public B,C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = new D();
    a->eat();
}

Если бы это будет работать, будет ли это увеличить a в B или a в C? Вот почему это неоднозначно. То this Указатель и любой нестатический элемент данных отличаются для двух A SubObjects (один из которых содержится в B suboubject, а другой по C suboubject). Попробуйте изменить свой код, как это будет работать (в том, что он компилирует и печатает «A»)

class A             { public: void eat(){ cout<<"A";} };
class B: public A   { public: void eat(){ cout<<"B";} };
class C: public A   { public: void eat(){ cout<<"C";} };
class D: public B, public C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = static_cast<B*>(new D());
      // A *a = static_cast<C*>(new D());
    a->eat();
}

Это позвонит eat на A подбор B и C соответственно.

Другие советы

Алмаз приводит к двум случаям a в объекте d, и он неоднозначен, какой из них вы ссылаетесь - вам нужно использовать виртуальное наследование, чтобы решить это:

class B: virtual public A   { public: void eat(){ cout<<"B";} };
class C: virtual public A   { public: void eat(){ cout<<"C";} };

Предполагая, что вы на самом деле хотели только один экземпляр. Я также предполагаю, что вы действительно имели в виду:

class D: public B, public C { public: void eat(){ cout<<"D";} };

Обратите внимание, что ошибка компиляции находится на «A * A = NEW D ();» Линия, не на звонок, чтобы «есть».

Проблема в том, что поскольку вы использовали не виртуальное наследование, вы в конечном итоге с классом дважды: один раз через B, а один раз через C. Если например, вы добавляете элемент M до A, то D имеет два из них: B :: м, а C :: м.

Иногда вы действительно хотите иметь дважды в графике деривации, в этом случае вам всегда нужно указывать, о котором вы говорите. В D вы сможете ссылаться на B :: M и C :: M отдельно.

Иногда, хотя вы действительно хотите только один а, в этом случае вам нужно использовать Виртуальное наследование.

Для действительно необычной ситуации ответ Neil на самом деле неверно (по крайней мере, частично).

С из Виртуальное наследование, вы получаете два отдельных копии A в конечном объекте.

«Алмаз» приводит к одной копии A в конечном объекте и производится по Использование виртуального наследования:

alt text

Так как «алмаз» означает, что есть только один копия A в финальном объекте ссылка на A не производит неоднозначность. Без виртуального наследования, ссылка на A может относиться к одному из двух разных объектов (тот, кто слева или тот, кто справа на диаграмме).

Ошибка, которую вы получаете не отзывающую eat() - Это приходит с линии раньше. Это самоуправление, которое создает двусмысленность. Как указывает Нил Баттерворт, есть два копии A в твоей D, и компилятор не знает, какой из них вы хотите a указывать на.

Вы хотите: (Достижимо с виртуальным наследством)

Подразделение
  / \
ДО Н.Э
  \ /
А.

И нет: (Что происходит без виртуального наследования)

Подразделение
   /   \
ДО Н.Э
  |     |
А.

Виртуальное наследование означает, что будет всего 1 экземпляр базы A Класс не 2.

Твой тип D будет иметь 2 указателя VTable (вы можете увидеть их на первой диаграмме), один для B и один за C Кто практически наследует A. DРазмер объекта увеличивается, потому что он хранит 2 указателя сейчас; Однако есть только один A Теперь.

Так B::A и C::A одинаковы, и поэтому не могут быть неоднозначных звонков от D. Отказ Если вы не используете виртуальное наследование, у вас есть вторая диаграмма выше. И любой звонок для члена A затем становится неоднозначным, и вам нужно указать, какой путь вы хотите взять.

У Википедии есть еще одна хорошая подводка и пример здесь

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top