55
class A      { public: void eat(){ cout<<"A";} }; 
class B: virtual public A { public: void eat(){ cout<<"B";} }; 
class C: virtual 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(); 
} 

Entiendo el problema del diamante, y la parte de arriba del código no tiene ese problema.¿Cómo soluciona la herencia virtual la ambigüedad de "diamante" (herencia múltiple)?

¿Cómo soluciona exactamente la herencia virtual el problema?

lo que entiendo: Cuando digo A *a = new D();, el compilador quiere saber si un objeto de tipo D se puede asignar a un puntero de tipo A, pero tiene dos caminos que puede seguir, pero no puede decidir por sí mismo.

Entonces, ¿cómo la herencia virtual resuelve el problema (el compilador de ayuda toma la decisión)?

Respuesta

60

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 base A clase 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

+0

puntero Vtable es un detalle de implementación. No todos los compiladores introducirán punteros vtable en este caso. – curiousguy

+10

Creo que se vería mejor si los gráficos se reflejaran verticalmente. En la mayoría de los casos he encontrado tales diagramas de herencia para mostrar las clases derivadas debajo de las bases. (Consulte "downcast", "upcast") – peterh

27

Las instancias de clases derivadas "contienen" instancias de clases de bases, para que se vean en la memoria como que:

class A: [A fields] 
class B: [A fields | B fields] 
class C: [A fields | C fields] 

Así, sin herencia virtual, instancia de la clase D se vería así:

class D: [A fields | B fields | A fields | C fields | D fields] 
      '- derived from B -' '- derived from C -' 

Por lo tanto, tenga en cuenta dos "copias" de A datos. herencia virtual significa que la clase dentro derivado hay un conjunto puntero vtable en tiempo de ejecución que apunta a los datos de la clase base, de modo que los casos de B, las clases C y D parecen:

class B: [A fields | B fields] 
      ^---------- pointer to A 

class C: [A fields | C fields] 
      ^---------- pointer to A 

class D: [A fields | B fields | C fields | D fields] 
      ^---------- pointer to B::A 
      ^--------------------- pointer to C::A 
+0

¿Se establecerá el puntero vtable en el tiempo de ejecución? – Balu

7

El problema no es el camino el compilador debe seguir. El problema es el punto final de esa ruta: el resultado del reparto. Cuando se trata de escribir conversiones, la ruta no importa, solo el resultado final sí.

Si usa la herencia ordinaria, cada ruta tiene su propio punto final distintivo, lo que significa que el resultado del reparto es ambiguo, que es el problema.

Si usa la herencia virtual, obtiene una jerarquía con forma de diamante: ambas rutas conducen al mismo punto final. En este caso, el problema de elegir la ruta ya no existe (o, más precisamente, ya no importa), ya que ambas rutas conducen al mismo resultado. El resultado ya no es ambiguo, eso es lo que importa. La ruta exacta no.

+0

@Andrey: ¿Cómo implementa el compilador la herencia ... quiero decir que recibo su argumento y quiero agradecerle por explicarlo con tanta lucidez ... pero sería realmente útil si puede explicar (o señalar a una referencia) como a cómo el compilador realmente implementa la herencia y qué cambia cuando hago la herencia virtual – Bruce

5

En realidad, el ejemplo debe ser la siguiente:

#include <iostream> 

//THE DIAMOND PROBLEM SOLVED!!! 
class A      { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; 
class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; 
class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; 
class D: public   B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} }; 

int main(int argc, char ** argv){ 
    A *a = new D(); 
    a->eat(); 
    delete a; 
} 

... de esa manera la salida va a ser la correcta: "EAT => D"

herencia virtual sólo se resuelve la duplicación de ¡el abuelo! pero todavía se necesita especificar los métodos a ser virtual con el fin de conseguir los métodos overrided correctamente ...

-2
#include <iostream> 

class A      { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; 
class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; 
class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; 
class D: public   B,C { public: virtual ~D(){ } }; 

int main(int argc, char ** argv){ 
    A *a = new D(); 
    a->eat(); 
    delete a; 
} 
+2

si elimina la función virtual void eat() {std :: cout <<"EAT=> D ";} de la clase D el problema DIAMOND viene de nuevo arrojando error error: no hay un único overrider final para 'virtual void A :: eat()' en 'D' –

-1

Este problema se puede resolver mediante el uso de palabras clave virtual.

A 
/\ 
B C 
\/ 
    D 

Ejemplo de Diamante Problema.

#include<stdio.h> 
using namespace std; 
class AA 
{ 
    public: 
      int a; 
     AA() 
      { 
       a=10; 
      } 
}; 
class BB: virtual public AA 
{ 
    public: 
      int b; 
     BB() 
      { 
       b=20; 
      } 
}; 
class CC:virtual public AA 
{ 
    public: 
      int c; 
     CC() 
      { 
       c=30; 
      } 
}; 
class DD:public BB,CC 
{ 
    public: 
      int d; 
     DD() 
      { 
       d=40; 
       printf("Value of A=%d\n",a);     
      } 
}; 
int main() 
{ 
    DD dobj; 
    return 0; 
}