Pergunta

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

Não tenho certeza de que isso se chama Diamond Problem ou não, mas por que isso não funciona?

Eu dei a definição para eat() por D. Então, também não precisa usar B's ou Ccópia (então, não deve haver problema).

Quando eu disse, a->eat() (lembrar eat() não é virtual), existe apenas um possível eat() para ligar, o de A.

Por que então eu recebo este erro:

'A' é uma base ambígua de 'd'


O que exatamente faz A *a = new D(); significa para o compilador ??

e

Por que o mesmo problema não ocorre quando eu uso D *d = new D();?

Foi útil?

Solução

Imagine um cenário um pouco diferente

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();
}

Se isso funcionasse, aumentaria o a dentro B ou o a dentro C? É por isso que é ambíguo. o this ponteiro e qualquer membro de dados não estáticos é distinto para os dois A subobjetos (um dos quais está contido pelo B subobjeto e o outro pelo C subobjeto). Tente alterar seu código como este e funcionará (na medida em que compila e imprime "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();
}

Isso vai ligar eat no A Subobjeto de B e C respectivamente.

Outras dicas

O diamante resulta em duas instâncias de A no objeto D, e é ambíguo a que você está se referindo - você precisa usar a herança virtual para resolver isso:

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

Supondo que você realmente quis apenas uma instância. Eu também suponho que você realmente quis dizer:

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

Observe que o erro de compilação está no "a *a = new d ();" linha, não na chamada para "comer".

O problema é que, como você usou herança não virtual, acaba com a Classe A duas vezes: uma vez a B, e uma vez por C. Se, por exemplo, adicionar um membro M a A, então D tem dois deles: B :: m e c :: m.

Às vezes, você realmente deseja ter uma duas vezes no gráfico de derivação; nesse caso, sempre precisa indicar de qual está falando. Em D, você seria capaz de fazer referência a B :: M e C :: M separadamente.

Às vezes, porém, você realmente quer apenas um a, nesse caso, você precisa usar herança virtual.

Para uma situação verdadeiramente incomum, a resposta de Neil está realmente errada (pelo menos em parte).

Com Fora herança virtual, você obtém duas cópias separadas de A no objeto final.

"The Diamond" resulta em uma única cópia de A no objeto final e é produzido por Usando herança virtual:

alt text

Já que "The Diamond" significa que há apenas 1 cópia de A no objeto final, uma referência a A não produz ambiguidade. Sem herança virtual, uma referência a A poderia se referir a um dos dois objetos diferentes (o da esquerda ou da direita no diagrama).

O erro que você está recebendo não está vindo de ligar eat() - Está vindo da linha antes. É a própria Upcast que cria a ambiguidade. Como Neil Butterworth aponta, existem duas cópias de A na tua D, e o compilador não sabe qual você deseja a apontar para.

Você quer: (Alcançável com herança virtual)

D
  / \
B c
  \ /
UMA

E não: (O que acontece sem herança virtual)

D
   /   \
B c
  |     |
A

Herança virtual significa que haverá apenas 1 instância da base A classe não 2.

Seu tipo D teria 2 ponteiros vtable (você pode vê -los no primeiro diagrama), um para B e um para C que praticamente herdam A. DO tamanho do objeto do objeto é aumentado porque armazena 2 ponteiros agora; No entanto, existe apenas um A agora.

Então B::A e C::A são iguais e, portanto, não pode haver chamadas ambíguas de D. Se você não usa a herança virtual, terá o segundo diagrama acima. E qualquer chamada para um membro de A então se torna ambígua e você precisa especificar qual caminho deseja seguir.

A Wikipedia tem outro bom resumo e exemplo aqui

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top