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 el código anterior no tiene ese problema.
¿Cómo resuelve exactamente el problema la herencia virtual?
Lo que entiendo:
cuando digo A *a = new D();, el compilador quiere saber si un objeto de tipo Dse 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 resuelve la herencia virtual el problema (ayude al compilador a tomar la decisión)?

BoCen su lugar? ¡Gracias!Las instancias de clases derivadas "contienen" instancias de clases base, por lo que se ven así en la memoria:
Por lo tanto, sin herencia virtual, la instancia de la clase D se vería así:
Por lo tanto, observe dos "copias" de los datos A. La herencia virtual significa que dentro de la clase derivada hay un puntero vtable configurado en tiempo de ejecución que apunta a los datos de la clase base, por lo que las instancias de las clases B, C y D se ven así:
fuente
¿Por qué otra respuesta?
Bueno, muchas publicaciones en SO y artículos externos dicen que el problema del diamante se resuelve creando una sola instancia de en
Alugar de dos (una para cada padre deD), resolviendo así la ambigüedad. Sin embargo, esto no me dio una comprensión completa del proceso, terminé con más preguntas comoBeCintenta crear diferentes instancias de,Apor ejemplo, llamar a un constructor parametrizado con diferentes parámetros (D::D(int x, int y): C(x), B(y) {})? ¿De qué instanciaAse elegirá para formar parteD?B, pero virtual paraC? ¿Es suficiente para crear una sola instancia deAinD?No poder predecir el comportamiento sin probar ejemplos de código significa no comprender el concepto. A continuación se muestra lo que me ayudó a comprender la herencia virtual.
Doble a
Primero, comencemos con este código sin herencia virtual:
Veamos la salida. La ejecución
B b(2);creaA(2)como se esperaba, lo mismo paraC c(3);:D d(2, 3);necesita ambosByC, cada uno de ellos creando el suyoA, por lo que tenemos dobleAend:Esa es la razón para
d.getX()causar un error de compilación, ya que el compilador no puede elegir para quéAinstancia debe llamar al método. Aún así, es posible llamar a métodos directamente para la clase principal elegida:Virtualidad
Ahora agreguemos herencia virtual. Usando el mismo ejemplo de código con los siguientes cambios:
Saltemos a la creación de
d:Puede ver que
Ase crea con el constructor predeterminado ignorando los parámetros pasados de los constructores deByC. Una vez que la ambigüedad desaparece, todas las llamadasgetX()devuelven el mismo valor:Pero, ¿y si queremos llamar al constructor parametrizado
A? Se puede hacer llamándolo explícitamente desde el constructor deD:Normalmente, la clase puede usar explícitamente solo constructores de padres directos, pero hay una exclusión para el caso de herencia virtual. Descubrir esta regla me hizo "clic" y me ayudó a comprender mucho las interfaces virtuales:
El código
class B: virtual Asignifica que cualquier clase heredadaBahora es responsable de crearApor sí misma, yaBque no lo hará automáticamente.Con esta afirmación en mente, es fácil responder todas las preguntas que tenía:
Dcreación niBniCes responsable de los parámetros deA, depende totalmente deDsolo.Cdelegará la creación deAaD, peroBcreará su propia instancia deAdevolver así el problema del diamantefuente
El problema no es la ruta que debe seguir el compilador. El problema es el punto final de ese camino: el resultado del reparto. Cuando se trata de conversiones de tipos, la ruta no importa, solo importa el resultado final.
Si usa herencia ordinaria, cada ruta tiene su propio punto final distintivo, lo que significa que el resultado de la conversión es ambiguo, que es el problema.
Si usa la herencia virtual, obtiene una jerarquía en forma de diamante: ambas rutas conducen al mismo punto final. En este caso, el problema de elegir el camino ya no existe (o, más precisamente, ya no importa), porque ambos caminos conducen al mismo resultado. El resultado ya no es ambiguo, eso es lo que importa. El camino exacto no lo hace.
fuente
En realidad, el ejemplo debería ser el siguiente:
... de esa manera la salida será la correcta: "EAT => D"
¡La herencia virtual solo resuelve la duplicación del abuelo! PERO todavía necesita especificar los métodos para que sean virtuales para que los métodos se anulen correctamente ...
fuente