2010-04-17 9 views
7
#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(); 
} 

No estoy seguro de que esto se llame problema de diamante o no, pero ¿por qué no funciona?¿Por qué hay ambigüedad en este patrón de diamantes?

He dado la definición de eat() para D. Por lo tanto, no es necesario utilizar la copia de B o C (por lo tanto, no debería haber ningún problema).

Cuando dije, a->eat() (recuerda eat() no es virtual), sólo hay una posible eat() a llamar, la de A.

¿Por qué entonces, qué recibo este error:

'A' is an ambiguous base of 'D'


¿Qué significa exactamente A *a = new D(); al compilador ??

y

¿Por qué el mismo problema se produce cuando se utiliza D *d = new D();?

+0

http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.9 – JRL

Respuesta

2

imaginar un escenario ligeramente 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(); 
} 

Si esto funcionaría, sería incrementar el a en B o la a en C? Es por eso que es ambiguo.El puntero this y cualquier miembro de datos no estáticos son distintos para los dos subobjetos A (uno de los cuales está contenido en el subobjeto B, y el otro en el subobjeto C). Intente cambiar su código como este y que va a funcionar (en el que se recopila 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(); 
} 

que llamará eat en el A subobjeto de B y C respectivamente.

+0

@Johannes Schaub - litb: bien, entiendo lo que dijiste. Pero ¿por qué no ocurre el mismo problema cuando uso 'D * d = new D();' – Moeb

+1

@cambr, la búsqueda de nombre se detiene cuando se hace 'd-> eat()' en el alcance de 'D' ya que encuentra 'comer' en' D'. No tocará el 'comer' en' A', 'B' o' C' y no intentará llamarlos. Por lo tanto, no hay conversión a 'A 'en ese caso, y por lo tanto, no aumenta la ambigüedad. –

+0

Si intentas 'd-> A :: eat()' tratando de llamar 'eat' en' A', obtendrías el mismo problema nuevamente ya que intentará convertir 'D *' a 'A * '. –

6

Los resultados de diamantes en dos casos de A en el objeto D, y es ambiguo, que uno se está refiriendo a - es necesario utilizar la herencia virtual para resolver esto:

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

suponiendo que en realidad sólo quería una instancia. También supongo que en realidad quería decir:

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

@Neil Butterworth: "copias de' A' en el objeto final"¿Por qué ¿Copiaría A en el objeto final? – Moeb

+0

@cambr Estaba usando "copiar" para decir "instancia" - Lo he cambiado. –

+0

@Neil Butterworth: Además, he definido 'eat()' explícitamente en D. Por lo tanto, no es necesario utilizar copias/instancia de 'B' o' C'. – Moeb

0

El error que está recibiendo no viene de llamar eat() - que viene de la línea antes. Es la propia subida lo que crea la ambigüedad. Como señala Neil Butterworth, hay dos copias de A en su D, y el compilador no sabe cuál desea que a apunte.

+0

@Mike: ¿por qué tengo 'copias de A en tu D'. He definido un 'comer()' separado para D. – Moeb

+0

No tiene nada que ver con 'comer()'; a la llamada 'a-> eat()' no le importa qué clases se derivan de 'A' (como dices,' eat' no es virtual) - siempre llamará 'A :: eat'. Como @Neil dice, elimine la llamada a 'comer' y todavía tiene el mismo problema. –

2

Tenga en cuenta que el error de compilación está en "A * a = new D();" línea, no en la llamada para "comer".

El problema es que debido a que usó herencia no virtual, termina con la clase A dos veces: una vez en B y una vez en C. Si, por ejemplo, agrega un miembro m a A, entonces D tiene dos : B :: m, y C :: m.

A veces, realmente desea tener A dos veces en el gráfico de derivación, en cuyo caso siempre debe indicar de qué A está hablando. En D, podría hacer referencia a B :: m y C :: m por separado.

A veces, sin embargo, solo desea una A, en cuyo caso debe usar virtual inheritance.

2

Para una situación verdaderamente inusual, la respuesta de Neil es realmente incorrecta (al menos en parte).

Con herencia virtual, obtiene dos copias separadas de A en el objeto final.

resultados "el diamante" en una sola copia de A en el objeto final, y es producido por utilizando la herencia virtual:

alt text

Desde "el diamante" significa que sólo hay uno copia de A en el objeto final, una referencia a A no produce ambigüedad. Sin herencia virtual, una referencia a A podría referirse a cualquiera de dos objetos diferentes (el de la izquierda o el de la derecha en el diagrama).

+0

Has dibujado un diagrama de objetos: mi respuesta "incorrecta" se refería al gráfico de herencia, que es un diamante. –

+0

@Jerry Coffin: ¿Por qué no ocurre el mismo problema cuando uso 'D * d = new D();'? En ese caso también, se está creando un objeto de tipo 'D' y los mismos problemas deberían ocurrir. – Moeb

+0

@cambr: porque 'A * a = new D();' no puede decidir a qué subobjeto 'A' apuntar. Con 'D * d = new D();', va a apuntar al objeto 'D' no a uno de los dos sujetos' A', por lo que no hay dudas sobre dónde debe apuntar. –

0

que desee: (alcanzable con la herencia virtual)

  D
 /\
B   C
  \ /
  A

Y no: (¿Qué ocurre sin la herencia virtual)

    D
  /  \
  B   C
  |     |
  A   A

herencia virtual significa que sólo habrá 1 instancia de la clase base A no 2.

Su tipo D tendría 2 punteros vtable (puede verlos en el primer diagrama), uno para B y otro para C que prácticamente heredan A. El tamaño del objeto D aumenta porque almacena 2 punteros ahora; sin embargo, ahora solo hay uno A ahora.

Así que B::A y C::A son lo mismo y no puede haber llamadas ambiguas desde D. Si no usa herencia virtual, tiene el segundo diagrama de arriba. Y cualquier llamada a un miembro de A se vuelve ambigua y debe especificar qué ruta desea tomar.

Wikipedia has another good rundown and example here

Cuestiones relacionadas