Pregunta

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

Me entender el problema de diamantes, y por encima de pieza de código no tiene ese problema.

¿Cómo funciona exactamente la herencia virtual a resolver 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 de resolver el problema (compilador de ayuda tomar la decisión)?

¿Fue útil?

Solución

¿Quieres: (alcanzable con la herencia virtual)

  A  
 / \  
B   C  
 \ /  
  D 

Y no: (Lo que ocurre sin la herencia virtual)

A   A  
|   |
B   C  
 \ /  
  D 

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

Su tipo D tendría 2 punteros vtable (se puede ver en el primer diagrama), uno para B y uno para C que A prácticamente hereda. el tamaño del objeto de D se incrementa, ya que almacena 2 punteros ahora; Sin embargo sólo hay un A ahora.

Así B::A y C::A son los mismos y por lo tanto no puede haber ninguna llamada ambiguas de D. Si no se utiliza la herencia virtual que tiene el segundo diagrama anterior. Y cualquier llamada a un miembro de A continuación, se vuelve ambigua y hay que especificar cuál es el camino que desea tomar.

Wikipedia tiene otro buen resumen y el ejemplo aquí

Otros consejos

Las instancias de clases derivadas "contiene" 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]

Por lo tanto, sin la herencia virtual, la 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 -'

Así, nota dos "copias" de un datos. medios herencia virtual que clase derivada en el interior hay un conjunto puntero vtable en tiempo de ejecución que los puntos a los datos de la clase base, de modo que las instancias de clases B, C y D se parecen a:

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

¿Por qué otra respuesta?

Bueno, muchos puestos en el exterior por ejemplo SO y artículos, ese problema se resuelve de diamantes mediante la creación de una sola instancia de A en lugar de dos (uno para cada padre de D), por lo que la resolución de la ambigüedad. Sin embargo, esto no me dio comprensión global de proceso, que terminó con más preguntas como

  1. ¿y si B y C trata de crear diferentes instancias de A por ejemplo llamando parametrizada constructor con diferentes parámetros (D::D(int x, int y): C(x), B(y) {})? Qué instancia de A serán elegidos para formar parte de D?
  2. ¿y si yo uso la herencia no virtual para B, pero uno virtual para C? ¿Es suficiente para crear una sola instancia de A en D?
  3. Siempre debe utilizar la herencia virtual por defecto a partir de ahora como medida preventiva, ya que resuelve posible problema de diamante con un rendimiento menor costo y no hay otros inconvenientes?

No ser capaz de predecir el comportamiento sin tratar de muestras de código significa que no entiende el concepto. A continuación se muestra lo que me ayudó a envolver la cabeza alrededor de la herencia virtual.

Doble A

En primer lugar, vamos a empezar con este código sin herencia virtual:

#include<iostream>
using namespace std;
class A {
public:
    A()                { cout << "A::A() "; }
    A(int x) : m_x(x)  { cout << "A::A(" << x << ") "; }
    int getX() const   { return m_x; }
private:
    int m_x = 42;
};

class B : public A {
public:
    B(int x):A(x)   { cout << "B::B(" << x << ") "; }
};

class C : public A {
public:
    C(int x):A(x) { cout << "C::C(" << x << ") "; }
};

class D : public C, public B  {
public:
    D(int x, int y): C(x), B(y)   {
        cout << "D::D(" << x << ", " << y << ") "; }
};

int main()  {
    cout << "Create b(2): " << endl;
    B b(2); cout << endl << endl;

    cout << "Create c(3): " << endl;
    C c(3); cout << endl << endl;

    cout << "Create d(2,3): " << endl;
    D d(2, 3); cout << endl << endl;

    // error: request for member 'getX' is ambiguous
    //cout << "d.getX() = " << d.getX() << endl;

    // error: 'A' is an ambiguous base of 'D'
    //cout << "d.A::getX() = " << d.A::getX() << endl;

    cout << "d.B::getX() = " << d.B::getX() << endl;
    cout << "d.C::getX() = " << d.C::getX() << endl;
}

Vamos a ir a través de la salida. La ejecución de B b(2); crea A(2) como se esperaba, lo mismo para C c(3);:

Create b(2): 
A::A(2) B::B(2) 

Create c(3): 
A::A(3) C::C(3) 

D d(2, 3); necesita tanto B y C, cada uno de ellos la creación de su propia A, por lo que tenemos doble A en d:

Create d(2,3): 
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3) 

Esa es la razón de d.getX() a causa error de compilación como compilador no puede elegir qué instancia A debe llamar al método para. Todavía es posible llamar a métodos directamente para la clase padre seleccionado:

d.B::getX() = 3
d.C::getX() = 2

virtualidad

Ahora vamos a añadir la herencia virtual. Usando el mismo ejemplo de código con los siguientes cambios:

class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...

Le permite saltar a la creación de d:

Create d(2,3): 
A::A() C::C(2) B::B(3) D::D(2, 3) 

Se puede ver, A se crea con el constructor por defecto haciendo caso omiso de los parámetros pasados ??desde constructores de B y C. A medida que la ambigüedad se ha ido, todas las llamadas a getX() devuelven el mismo valor:

d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42

Pero lo que si queremos llamar constructor parametrizado para A? Se puede hacer mediante una llamada explícita que desde el constructor de D:

D(int x, int y, int z): A(x), C(y), B(z)

Normalmente, la clase puede utilizar de forma explícita constructores de sólo los padres directos, pero no es una exclusión para el caso de la herencia virtual. El descubrimiento de esta regla "clic" para mí y ayudado a entender las interfaces virtuales mucho:

código significa class B: virtual A, que cualquier clase heredada de B es ahora responsable de la creación de A por sí mismo, ya que B no va a hacerlo de forma automática.

Con esta declaración en cuenta que es fácil de responder todas las preguntas que tenía:

  1. Durante la creación ni D ni B C es responsable de los parámetros de A, es totalmente hasta D solamente.
  2. C delegará creación de A a D, pero B creará su propia instancia de A llevando así problemas de espalda de diamante
  3. Definición de los parámetros de la clase base en la clase nieto en lugar de hijo directo no es una práctica buena, por lo que debe ser tolerado cuando existe un problema de diamantes y esta medida es inevitable.

El problema no es la ruta el compilador debe seguir. El problema es el punto final de ese camino: el resultado del reparto. Cuando se trata de escribir las conversiones, el camino no importa, sólo el resultado final lo hace.

Si utiliza la herencia común, cada camino tiene su propio punto final distintivo, lo que significa que el resultado del reparto es ambigua, que es el problema.

Si utiliza la herencia virtual, se obtiene una jerarquía en forma de diamante: ambos caminos conduce al mismo punto final. En este caso existe el problema de elegir el camino ya no (o, más precisamente, ya no importa), debido a que ambos caminos conducen al mismo resultado. El resultado ya no es ambigua - que es lo que importa. La ruta exacta no lo hace.

En realidad, el ejemplo debe ser como sigue:

#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 resuelve la duplicación del abuelo! Pero todavía se necesita especificar los métodos a ser virtual con el fin de conseguir los métodos correctamente overrided ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top