5

A menudo utilizo clases virtuales puras (interfaces) para reducir las dependencias entre implementaciones de diferentes clases en mi proyecto actual. No es inusual para mí incluso tener jerarquías en las que tengo clases virtuales puras y no puras que extienden otras clases virtuales puras. Este es un ejemplo de tal situación:¿Es una buena convención heredar virtualmente de las clases virtuales puras (interfaz)?

class Engine 
{ /* Declares pure virtual methods only */ } 

class RunnableEngine : public virtual Engine 
{ /* Defines some of the methods declared in Engine */ } 

class RenderingEngine : public virtual Engine 
{ /* Declares additional pure virtual methods only */ } 

class SimpleOpenGLRenderingEngine : public RunnableEngine, 
    public virtual RenderingEngine 
{ /* Defines the methods declared in Engine and RenderingEngine (that are not 
    already taken care of by RunnableEngine) */ } 

Tanto RunnableEngine y RenderingEngine extienden Engine prácticamente de manera que el problema de diamante no afecta SimpleOpenGLRenderingEngine.

Quiero tomar una postura preventiva contra el problema del diamante en lugar de tratarlo cuando se convierte en un problema, especialmente porque me gusta escribir un código que sea tan fácil de usar como sea posible para otra persona y no quiero ellos tienen que modificar mis clases para que puedan crear jerarquías de clases particulares, por ejemplo si Bob quería hacer esto:

class BobsRenderingEngine : public virtual RenderingEngine 
{ /* Declares additional pure virtual methods only */ } 

class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine, 
    public BobsRenderingEngine 
{ /* Defines the methods declared in BobsRenderingEngine */ } 

Esto no sería posible si no hubiera hecho SimpleOpenGLRenderingEngine se extienden prácticamente RenderingEngine. Soy consciente de que la probabilidad de que Bob quiera hacer esto puede ser muy baja.

Por lo tanto, he comenzado a utilizar la convención de extender siempre las clases virtuales puras virtualmente para que la herencia múltiple de ellas no cause el problema del diamante. Tal vez esto se deba a que procedo de Java y tengo tendencia a usar solo herencia individual con clases virtuales no puras. Estoy seguro de que probablemente sea excesivo en algunas situaciones, pero ¿hay algún inconveniente para usar esta convención? ¿Podría esto causar algún problema con el rendimiento/funcionalidad, etc.? Si no, no veo una razón para no usar la convención, incluso si a menudo no es necesaria al final.

+3

Sí, completa la sobrecarga. Solo es un verdadero problema de diamantes cuando tiene múltiples * implementaciones * para elegir. No es un problema con una interfaz, no tiene ninguna implementación. La herencia virtual es una curita, solo la usas cuando tu espalda está contra la pared. –

+2

@HansPassant, no estoy de acuerdo con usted. El problema de Dimond a menudo puede ocurrir incluso con clases virtuales puras. Supongamos que tiene una interfaz, digamos IA, y su implementación predeterminada 'AImpl: IA virtual pública', y luego usando herencia virtual puede definir una 'clase B: IA virtual pública, AImpl pública '. Por lo tanto, B usará la implementación de AImpl de IA. –

+0

@kids_fok Sí, ahí es exactamente donde tengo el problema. Bueno, no estoy seguro de que necesites 'B' para extender' IA' explícitamente en tu ejemplo con el ejemplo en mi pregunta es similar. –

Respuesta

2

Yo diría que, en general, alentar la herencia es una mala idea (y eso es lo que está haciendo al usar herencia virtual). Pocas cosas están realmente bien representadas con una estructura de árbol que está implícita en la herencia. Además, encuentro que la herencia múltiple tiende a romper la regla de responsabilidad única. No conozco su caso de uso exacto y estoy de acuerdo en que a veces no tenemos otra opción.

Sin embargo, en C++ tenemos otra forma de componer objetos, la encapsulación. Me parece que la declaración a continuación es mucho más fácil de comprender y manipular:

class SimpleOpenGLRenderingEngine 
{ 
public: 
    RunnableEngine& RunEngine() { return _runner; } 
    RenderingEngine& Renderer() { return _renderer; } 

    operator RunnableEngine&() { return _runner; } 
    operator RenderingEngine&() { return _renderer; } 

private: 
    RunnableEngine _runner; 
    RenderingEngine _renderer; 
}; 

Es cierto que el objeto va a utilizar más memoria que con la herencia virtual, pero dudo de que los objetos complejos se crean de forma masiva.

Digamos que realmente desea heredar, por alguna restricción externa. La herencia virtual todavía es difícil de manipular: probablemente te sientas cómodo con ella, pero las personas que derivan de tus clases pueden no tenerlo, y probablemente no piensen demasiado antes de derivar. Supongo que una mejor opción sería usar herencia privada.

class RunnableEngine : private Engine 
{ 
    Engine& GetEngine() { return *this; } 
    operator Engine&() { return *this; } 
}; 

// Similar implementation for RenderingEngine 

class SimpleOpenGLRenderingEngine : public RunnableEngine, public RenderingEngine 
{ }; 
2

Respuesta corta: Sí. Lo lograste.

Respuesta larga:

Por lo tanto, he comenzado a utilizar la convención de que se extienden siempre clases virtuales puras

Nota: el término adecuado es abstractos clases, no "clases virtuales puras ".

He empezado a utilizar la convención de extender siempre las clases [abstractas] de forma que su herencia múltiple no provoque el problema del diamante.

Indeed. Esta es una convención de sonido, pero no solo para las clases abstractas, para todas las clases, que representa una interfaz, algunas de las cuales tienen funciones virtuales con implementaciones predeterminadas.

(Las deficiencias de la fuerza de Java para poner solamente las funciones virtuales puras y ningún miembro de datos en "interfaces", C++ es más flexible, para bien, como de costumbre.)

Podría esta causa ningún problema con rendimiento

Virtual-ness tiene un costo.

Perfile su código.

¿Esto podría causar problemas con la funcionalidad [], etc.?

Posiblemente (rara vez).

No se puede desvirtualizar una clase base: si alguna clase derivada específica necesita tener dos subobjetos de clase base distintos, no puede usar la herencia para ambas ramas, necesita contención.

1

Bueno, es bastante simple. Usted violó el principio de responsabilidad única (SPR) y esta es la causa de su problema. Tu clase de "Motor" es una clase de Dios. Una jerarquía bien diseñada no invoca este problema.

Cuestiones relacionadas