Почему есть двусмысленность в этом алмазном рисунке?
-
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
в конечном объекте и производится по Использование виртуального наследования:
Так как «алмаз» означает, что есть только один копия 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 затем становится неоднозначным, и вам нужно указать, какой путь вы хотите взять.