Почему там неоднозначность в этой схеме размещения алмазов?

#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();?

7
задан Moeb 17 April 2010 в 14:35
поделиться

6 ответов

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

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 (один из которых содержится в подобъекте B , а другой - в подобъект C ). Попробуйте изменить свой код таким образом, и он будет работать (компилируется и печатает "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 соответственно.

2
ответ дан 6 December 2019 в 21:11
поделиться

Ромб дает ДВА экземпляра A в объекте D, и неясно, какой из них вы имеете в виду - вам нужно использовать виртуальное наследование для решения this:

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";} };
6
ответ дан 6 December 2019 в 21:11
поделиться

Обратите внимание, что ошибка компиляции связана с "A * a = new D ();" линия, а не на призыв "есть".

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

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

Однако иногда вам действительно нужен только один A, и в этом случае вам нужно использовать виртуальное наследование .

2
ответ дан 6 December 2019 в 21:11
поделиться

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

0
ответ дан 6 December 2019 в 21:11
поделиться

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

D
/ \
B C
\ /
A

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

D
/ \
B C
| |
A A

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

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

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

В Википедии есть еще одно хорошее краткое изложение и пример здесь

0
ответ дан 6 December 2019 в 21:11
поделиться

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

При виртуальном наследовании out вы получаете две отдельные копии A в конечном объекте.

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

alt text

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

2
ответ дан 6 December 2019 в 21:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: