2010-08-05 12 views
10

Entiendo los conceptos básicos de la herencia virtual de C++. Sin embargo, estoy confundido sobre dónde exactamente necesito usar la palabra clave virtual con una jerarquía de clase compleja. Por ejemplo, supongamos que tengo las siguientes clases:¿Dónde es necesaria la palabra clave "virtual" en una compleja jerarquía de herencia múltiple?

  A 
     /\ 
      B C 
     /\/\ 
     D E F 
     \/\/
      G H 
      \/
      I 

Si quiero asegurar que ninguna de las clases aparecer más de una vez en cualquiera de las subclases, que deben ser marcados virtual clases base? ¿Todos ellos? ¿O es suficiente usarlo solo en aquellas clases que se derivan directamente de una clase que de otro modo podría tener múltiples instancias (es decir, B, C, D, E y F, y G y H (pero solo con la clase base E, no con las clases base D y F))?

+0

¿Es esta una pregunta teórica o acabaste en una situación en la que esto tiene un valor real para ti? :) – falstro

+0

En realidad, tenemos jerarquías de clases que se ven así (de hecho, son aún peores). Se planifica una refacturación ... – jchl

+2

¡¡Es un escenario de herencia :)! – DumbCoder

Respuesta

7

Tiene que especificar la herencia virtual al heredar de cualquiera de las clases A, B, C y E (que se encuentran en la parte superior de un diamante).

class A; 
class B: virtual A; 
class C: virtual A; 
class D: virtual B; 
class E: virtual B, virtual C; 
class F: virtual C; 
class G:   D, virtual E; 
class H: virtual E,   F; 
class I:   G,   H; 
+0

Gracias, eso tiene sentido ahora. Como pregunta de seguimiento, ¿puedes explicar qué (si acaso) sería el efecto de utilizar la herencia virtual en cualquier lugar? ¿Nada? ¿O terminaría con un diseño diferente (más grande) para las instancias de I? – jchl

+1

El uso de la herencia virtual en todas partes ralentiza las cosas, un poco (aunque nunca he medido). El principal inconveniente de la herencia virtual en mi humilde opinión, es que una vez que has lanzado tu objeto a un puntero o referencia a una de sus bases virtuales, no puedes volver a lanzarlo a la clase heredada. –

+0

No me di cuenta de que no se podía bajar un puntero a una clase base virtual. Gracias por señalar (sin juego de palabras) eso. – jchl

2

Mi sugerencia personal sería comenzar en B y C: virtual A, y luego seguir agregando hasta que el compilador deje de quejarse.

En realidad, diría que B y C: virtual A, G y H: virtual E, y E: virtual B y C. Todos los otros enlaces de herencia pueden ser herencia normal. Sin embargo, esta monstruosidad tomaría como seis décadas para hacer una llamada virtual.

+0

También tiene que 'F: C virtual y' D: B virtual', de lo contrario H heredará virtual de C a E, y no- virtualy from F. –

+0

¿Puede explicar por qué solo aquellos necesitan ser virtuales? ¿Por qué no D: B y F virtual: C virtual? ¿Y por qué las llamadas a funciones virtuales serían lentas en dicha clase? – jchl

+0

Tienes razón, los que probablemente también necesiten ser virtuales. Realmente necesita verificarlo con su compilador.En cuanto a lento, porque hay seis docenas de tipos diferentes que el compilador necesita para verificar cada llamada de función virtual. – Puppy

0

Si usted quiere tener sólo una instancia "física" de cada tipo para cada instancia de cada tipo (sólo una A, B único, etc.) Sólo tendrá que usar la herencia virtual cada vez que se usar herencia.

Si desea instancias separadas de uno de los tipos, use la herencia normal.

+0

La pregunta es si * todo * la herencia debe ser virtual para dar la jerarquía requerida. La respuesta es no. –

23

Jugué un programa en conjunto que podría ayudarte a estudiar las complejidades de las bases virtuales. Imprime la jerarquía de clases bajo I como un dígrafo adecuado para graphiviz (http://www.graphviz.org/). Hay un contador para cada instancia que también te ayuda a comprender la orden de construcción. Aquí está la Programm:

#include <stdio.h> 
int counter=0; 



#define CONN2(N,X,Y)\ 
    int id; N() { id=counter++; }\ 
    void conn() \ 
    {\ 
     printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \ 
     printf("%s_%d->%s_%d\n",#N,this->id,#Y,((Y*)this)->id); \ 
     X::conn(); \ 
     Y::conn();\ 
    } 
#define CONN1(N,X)\ 
    int id; N() { id=counter++; }\ 
    void conn() \ 
    {\ 
     printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \ 
     X::conn(); \ 
    } 

struct A { int id; A() { id=counter++; } void conn() {} }; 
struct B : A { CONN1(B,A) }; 
struct C : A { CONN1(C,A) }; 
struct D : B { CONN1(D,B) }; 
struct E : B,C { CONN2(E,B,C) }; 
struct F : C { CONN1(F,C) }; 
struct G : D,E { CONN2(G,D,E) }; 
struct H : E,F { CONN2(H,E,F) }; 
struct I : G,H { CONN2(I,G,H) }; 
int main() 
{ 
    printf("digraph inh {\n"); 
    I i; 
    i.conn(); 
    printf("}\n"); 
} 

Si funciono esto (g++ base.cc ; ./a.out >h.dot ; dot -Tpng -o o.png h.dot ; display o.png), me sale el típico árbol de base no virtual: alt text http://i34.tinypic.com/2ns6pt4.png

añadiendo suficiente virtualidad ...

struct B : virtual A { CONN1(B,A) }; 
struct C : virtual A { CONN1(C,A) }; 
struct D : virtual B { CONN1(D,B) }; 
struct E : virtual B, virtual C { CONN2(E,B,C) }; 
struct F : virtual C { CONN1(F,C) }; 
struct G : D, virtual E { CONN2(G,D,E) }; 
struct H : virtual E,F { CONN2(H,E,F) }; 
struct I : G,H { CONN2(I,G,H) }; 

.. resultados en la forma del diamante (mira los números para aprender la orden de construcción !!)

alt text http://i33.tinypic.com/xpa2l5.png

Pero si haces todas las bases virtuales:

struct A { int id; A() { id=counter++; } void conn() {} }; 
struct B : virtual A { CONN1(B,A) }; 
struct C : virtual A { CONN1(C,A) }; 
struct D : virtual B { CONN1(D,B) }; 
struct E : virtual B, virtual C { CONN2(E,B,C) }; 
struct F : virtual C { CONN1(F,C) }; 
struct G : virtual D, virtual E { CONN2(G,D,E) }; 
struct H : virtual E, virtual F { CONN2(H,E,F) }; 
struct I : virtual G,virtual H { CONN2(I,G,H) }; 

obtienes un diamante con una orden de inicialización diferente:

alt text http://i33.tinypic.com/110dlj8.png

Diviértete!

+1

+1 Vaya, qué respuesta, tuve que preferir esta pregunta solo para asegurarme de tener un enlace a esto para futuras referencias. – bshields

+0

No es exactamente la explicación que estaba buscando, pero de todos modos es genial. – jchl

+0

+10 si fue posible ...;) - así que desafortunadamente puedo dar +1 solamente. – IanH

1

Si desea asegurarse de que un objeto de la clase superior en la jerarquía (I en su caso) contiene exactamente un subobjeto de cada clase padre, usted tiene que encontrar todas las clases en la jerarquía que tienen más de una superclase y hacer estas clases virtual bases de sus superclases. Eso es.

En sus clases de casos A, B, C y E tiene que convertirse en clases base virtuales cada vez que se hereda de ellos en esta jerarquía.

Las clases D, F, G y H no tienen que convertirse en clases base virtuales.

0

Editado: pensé que una era la clase más derivada;)

@ respuesta de Lutero realmente genial, pero de vuelta a la pregunta original:

Necesita utilizar virtual herencia al heredar de cualquier clase de la cual al menos otra clase hereda en la jerarquía de herencia (en los diagramas de Luther significa que al menos dos flechas apuntan a la clase).

Aquí es innecesario antes D, F, G y H porque sólo una clase se deriva de ellos (y ninguno se deriva de I por el momento).

Sin embargo, si no sabe de antemano si otra clase heredará o no de su clase base, puede agregar virtual como medida de precaución. Por ejemplo, se recomienda que una clase Exception herede virtualmente desde std::exception nada menos que con el propio Stroustrup.

Como ha señalado Luther, modifica la orden de creación de instancias (y tiene un ligero efecto en las actuaciones), pero yo consideraría que cualquier diseño que dependa de la orden de construcción es incorrecto para empezar. Y solo como precisión: usted todavía tiene las garantías de que las clases base se inicializan antes que cualquier atributo de la clase derivada, y por lo tanto antes de la ejecución del cuerpo constructor derivado.

+0

Mi pregunta original no estaba clara: la clase I (no A) es la clase más derivada. Así que creo que te refieres (como han dicho otros) que lo virtual es innecesario antes de D, F, G y H (y yo, aunque nadie se deriva de I). No me importa el orden de instanciación; Me importa el tamaño del objeto. Me gustaría evitar tener punteros vtable adicionales innecesarios en cada instancia. – jchl

+0

@jchl: Debe tener en cuenta que la herencia virtual se implementa normalmente incorporando punteros derivados a la base en el objeto para cada enlace virtual derivado a la base. En otras palabras, no ahorrará mucho en términos de datos ocultos del hogar cambiando a la herencia virtual. Por el contrario, con la herencia virtual, el funcionamiento interno generalmente se vuelve mucho más complicado. – AnT

+1

@AnT Los indicadores internos son solo una posible implementación y una pequeña optimización. No son necesarios ya que el vtable contiene toda la información. – curiousguy

0

Lo que hay que tener en cuenta es que C++ guarda una tabla de la herencia. Cuanto más agregue clases virtuales, mayor será el tiempo de compilación (vinculación) y el más pesado será el tiempo de ejecución.

En general, si se puede evitar la clase virtual, puede reemplazarla por algunas plantillas o tratar de desacoplar de alguna manera.

+0

¿Qué quiere decir con "tabla de la herencia"? – curiousguy

+0

C++ guarda un repositorio de la clase hiearchy para enlace dinámico. Cuanto más complejo es el repositorio de árbol, más alto se obtiene el tiempo de compilación y ejecución. –

Cuestiones relacionadas