tipos resueltos relaciones poo herencia entre ejercicios ejemplos clases c++ inheritance

resueltos - relaciones entre clases c++



¿Es la herencia múltiple de la misma clase base a través de diferentes clases padre realmente un problema aquí? (5)

Quiero implementar una jerarquía de herencia en mi motor de juego C ++ que tiene algunas analogías de Java:

  • todos los objetos heredados de la clase de Object ,
  • alguna funcionalidad es proporcionada por las interfaces.

Es solo una analogía que me brinda algunos beneficios ( no una asignación 1: 1 Java: C ++ estricta que no es posible).

Por "interfaz" me refiero aquí a una clase con solo métodos públicos puramente virtuales. Sé que no es una definición estricta y precisa de ello.

Así que he hecho:

class Object{...}; /* interfaces */ class Nameable : public Object{...}; class Moveable : public Object{...}; class Moveable3D : public Moveable{...}; class Moveable2D : public Moveable{...}; class Model3D : public Moveable3D, public Nameable{...} class Line3D : public Moveable3D{...} class Level: public Nameable{...} class PathSolver : public Object{...}

Como puede ver, Model3D ahora hereda de Object través de múltiples formas:

  • Moveable3D -> Moveable -> Object ,
  • Nameable -> Object .

Y mi compilador en VS2013 (VC ++) me advierte al respecto .

¿Es eso un problema?

Si es así, ¿cómo resolverlo para obtener una mejor estructura de herencia?

Estaba pensando en dos formas, pero ambas tienen más desventajas que ventajas:

  1. Eliminando la herencia entre, por ejemplo, Nameable y Object . Pero de esa manera Level también perdería su herencia de Object (y mi primer objetivo fue: todos los objetos heredan de la clase Object ).
  2. También podría soltar : public Object desde cada interfaz . Pero de esa manera me obligaría a escribir manualmente :public Object en cada una de las muchas clases finales de motor ( Model3D , Line3D , Level , PathSolver ). De esa manera, tanto la refactorización como la creación de nuevos tipos finales serían más difíciles. Además, la información que se garantiza que cada interfaz heredará de Object se pierde de esa manera.

Me gustaría evitar la herencia virtual (un nivel de abstracción adicional) si no es necesario. Pero tal vez sea (al menos para : public Object )?


La herencia virtual pondrá orden en su jerarquía de clases. Algunas cosas a tener en cuenta:

  • No mezcle la herencia virtual / no virtual para la misma clase base en su jerarquía a menos que realmente sepa lo que está haciendo.
  • El orden de inicialización se vuelve complicado. El constructor de la clase virtualmente heredada será llamado solo una vez. El constructor tomará los argumentos de la clase más derivada. Si no lo especifica explícitamente en Moveable3D, se llamará al constructor predeterminado, incluso si se pueden mover argumentos específicos para el constructor de Object. Por lo tanto, para evitar el desorden, asegúrese de usar solo constructores POR DEFECTO en todas las clases que herede virtualmente.

Primero asegúrese de que Model3D (que está usando herencia múltiple) tenga que proporcionar una interfaz desde sus clases base, es decir, que se Nameable , así como Model3D . Debido al nombre que ha proporcionado a su clase, me parece que Nameable debería implementarse mejor como miembro de ''Model3D'' .

Aparte de eso, la mayoría de los problemas en herencia múltiple vienen si las clases base tienen sus propios datos. Al igual que entonces, tiene que decidir si la clase base sería virtual O no, etc ... Sin embargo, mencionó que las clases base son todas interfaces, es decir, con al menos una función virtual pura. Entonces, creo que puedes evitar muchos problemas de herencia múltiple.


Sí, es un problema: en el diseño de memoria de Model3D , hay dos subobjetos de tipo Object ; uno es un subobjeto de Movable3D , el otro de Nameable . Como tal, estos dos subobjetos pueden contener datos diferentes. Esto es especialmente un problema, si la clase de objeto contiene una funcionalidad que tiene que ver con la identidad de un objeto. Por ejemplo, una clase de Object que implementa el conteo de referencias debe ser un subobjeto único de todos los objetos; un objeto que tiene dos recuentos de referencias diferentes es muy probablemente una muy mala idea ...

Para evitar esto, usa la herencia virtual. La única herencia virtual que se necesita es de la clase base Object :

class Objec{...]; class Nameable : public virtual Object{...}; class Moveable : public virtual Object{...}; class Moveable3D : public Moveable{...}; class Moveable2D : public Moveable{...}; class Model3D : public Movable3D, public Nameable{...}; ...


Utilizar herencia virtual de interfaces. Eso asegurará que solo haya una instancia de clase base en las clases derivadas:

class Object{...}; /* interfaces */ class Nameable : public virtual Object{...}; class Moveable : public virtual Object{...}; class Moveable3D : public virtual Moveable{...}; class Moveable2D : public virtual Moveable{...}; class Model3D : public virtual Moveable3D, public virtual Nameable{...} class Line3D : public virtual Moveable3D{...} class Level: public virtual Nameable{...} class PathSolver : public virtual Object{...}

Sin la herencia virtual, hay varias instancias de la misma base en el objeto que harán que no pueda sustituir el tipo concreto donde se necesita la interfaz:

void workWithObject(Object &o); Model3D model; workWithObject(model);


Como dijo Juraj Blaho, la forma canónica de resolver este problema es la herencia virtual. Un modelo de implementación para la herencia virtual es agregar una entrada en la tabla de clases usando la herencia virtual que apunta al objeto único real desde la clase base. De esa manera, obtienes el siguiente gráfico de herencia:

Model3D / / Moveable3D Nameable | | Moveable | / / Object

Eso es lo que debes tener:

class Nameable : public virtual Object{...}; class Moveable : public virtual Object{...};

Las otras clases no necesitan herencia virtual.

Sin herencia virtual la gráfica hubiera sido

Model3D / / Moveable3D Nameable | | Moveable | | | Object Object

con 2 instancias distintas de Objeto

La parte más complicada de la herencia virtual es la llamada de los constructores (ref. Herencia virtual en C ++, y la solución del problema de diamante ). Debido a que solo hay una única instancia de una clase base virtual compartida por varias clases que la heredan, el constructor para una clase base virtual no es llamada por la clase que la hereda (que es cómo se llama a los constructores, cuando cada clase tiene su propia copia de su clase primaria) ya que eso significaría que el constructor se ejecutaría varias veces. En cambio, el constructor es llamado por el constructor de la clase concreta. ... Por cierto, los constructores para las clases base virtuales siempre se llaman antes que los constructores para las clases base no virtuales. Esto garantiza que una clase heredada de una clase base virtual pueda estar segura de que la clase base virtual es segura para usar dentro del constructor de la clase hereditaria. El orden del destructor en una jerarquía de clases con una clase base virtual sigue las mismas reglas que el resto de C ++: los destructores se ejecutan en el orden opuesto de los constructores. En otras palabras, la clase base virtual será el último objeto destruido, ya que es el primer objeto que está completamente construido.

Pero el problema real es que este Dreadful Diamond on Derivation a menudo se considera como un mal diseño de jerarquía y está explícitamente prohibido en Java.

Pero en mi humilde opinión, lo que la herencia virtual de C ++ hace bajo el capó no está tan lejos de lo que tendría si todas sus clases de interfaz fueran puras clases abstractas (solo métodos públicos virtuales puros) no heredadas de Object y si todas sus clases de implementación heredaran explícitamente Objeto. Usarlo o no depende de usted: después de todo, es parte del lenguaje ...