2008-10-31 11 views
22

Tengo una clase base abstracta que actúa como interfaz.Herencia múltiple de dos clases derivadas

Tengo dos "conjuntos" de clases derivadas, que implementan la mitad de la clase abstracta. (un "conjunto" define los métodos virtuales abstractos relacionados con la inicialización, el otro "conjunto" define aquellos relacionados con el "trabajo" real).

Luego obtuve clases que usan herencia múltiple para construir clases completamente definidas (y no agrega nada en sí mismo).

Por lo tanto: (mala pseudocódigo)

class AbsBase { 
    virtual void init() = 0; 
    virtual void work() = 0; 
} 

class AbsInit : public AbsBase { 
    void init() { do_this(); } 
    // work() still abs 
} 

class AbsWork : public AbsBase { 
    void work() { do_this(); } 
    // init() still abs 
} 

class NotAbsTotal : public AbsInit, public AbsWork { 
    // Nothing, both should be defined 
} 

En primer lugar, puedo hacer esto? ¿Puedo heredar de dos clases derivadas de la misma base? (Eso espero).

Aquí está el "problema real", aunque (he mentido un poco más arriba para simplificar el ejemplo).

Lo que realmente me he ido y hecho es añadir métodos no abstractos descriptores de acceso a la clase base:

class AbsBase { 
public: 
    void init() { init_impl(); } 
    void work() { work_impl(); } 

private: 
    virtual void init_impl() = 0; 
    virtual void work_impl() = 0; 
} 

Porque, un idioma común es hacer que todos los métodos virtuales privadas.

Desafortunadamente, ahora AbsInit y AbsWork heredan estos métodos, por lo que NotAbsTotal hereda "dos de cada uno" (me doy cuenta de que puedo estar matando lo que realmente está sucediendo en el momento de la compilación).

De todos modos, g ++ se queja de que: "la solicitud para el miembro init() es ambigua" al intentar usar la clase.

Supongo que, si hubiera usado mi clase AbsBase como una interfaz pura, esto se habría evitado (suponiendo que el ejemplo superior sea válido).

Entonces: - ¿Estoy muy lejos con mi implementación? - ¿Es esto una limitación de la expresión idiomática de hacer que los métodos virtuales sean privados? - ¿Cómo refactorizo ​​mi código para hacer lo que quiero? (Proporcionar una interfaz común, pero permitir que una manera de intercambiar las implementaciones de "conjuntos" de las funciones miembro)

Editar:

parece que no soy el primero: http://en.wikipedia.org/wiki/Diamond_problem

Parece herencia virtual es la solución aquí. He oído hablar de herencia virtual antes, pero no me he acostumbrado a eso. Todavía estoy abierto a sugerencias.

Respuesta

33

Parece que quiere hacer herencia virtual. Ya sea que resulta ser en realidad una buena idea es otra cuestión, pero así es como se hace:


class AbsBase {...}; 
class AbsInit: public virtual AbsBase {...}; 
class AbsWork: public virtual AbsBase {...}; 
class NotAbsTotal: public AbsInit, public AbsWork {...}; 

Básicamente, el valor predeterminado, la herencia múltiple no virtual incluirá una copia de cada clase base en el clase derivada, e incluye todos sus métodos. Esta es la razón por la que tiene dos copias de AbsBase, y la razón por la que el uso de su método es ambiguo es porque ambos conjuntos de métodos están cargados, ¡así que C++ no tiene manera de saber a qué copia acceder!

La herencia virtual condensa todas las referencias a una clase base virtual en una estructura de datos. Esto debería hacer que los métodos de la clase base no sean ambiguos nuevamente. Sin embargo, tenga en cuenta: si hay datos adicionales en las dos clases intermedias, puede haber una pequeña sobrecarga de tiempo de ejecución adicional para permitir que el código encuentre la clase base virtual compartida.

+0

¿Por qué dices eso? ¿Cuáles son las implicaciones, aparte de la complejidad de la sintaxis, de la herencia virtual? – mmocny

+0

La conversión de un puntero base a una clase derivada normalmente implica ajustes de puntero, utilizando una tabla de búsqueda dinámica. Esta indirección adicional introduce un costo de conversión de base a derivado. Cuando se llama a un método virtual, esta sobrecarga está en el ruido de la llamada. –

1

Se puede hacer, aunque da la mayoría de los escalofríos.

Es necesario utilizar "la herencia virtual", la sintaxis para las que es algo así como

class AbsInit: public virtual AbsBase {...}; 
class AbsWork: public virtual AbsBase {...}; 
class NotAbsTotal: public AbsInit, public AbsWork {...}; 

Entonces usted tiene que especificar qué función que desea utilizar:

NotAbsTotal::work() 
{ 
    AbsInit::work_impl(); 
} 

(actualizado con correcta sintaxis)

1

Es necesario declarar la herencia como virtual:

struct AbsBase { 
      virtual void init() = 0; 
      virtual void work() = 0; 
}; 

struct AbsInit : virtual public AbsBase { 
      void init() { } 
}; 

struct AbsWork : virtual public AbsBase { 
      void work() { } 
}; 

struct NotAbsTotal : virtual public AbsInit, virtual public AbsWork { 
}; 

void f(NotAbsTotal *p) 
{ 
     p->init(); 
} 

NotAbsTotal x; 
+0

Esto funcionó bien, incluso con mis métodos públicos no virtuales. Sin embargo, tendré que tratar de ver qué pasa cuando uso un puntero de tipo base para un objeto de tipo Derivado, para asegurarme de que la herencia virtual funcione como sobrecarga de funciones virtuales :) – mmocny

+0

Al convertir de una base virtual a una clase derivada, siempre es necesario usar dynamic_cast <>, que a su vez requiere que se defina un método virtual en la base virtual.El uso simple de una base virtual no debería dar problemas: dará polimórficamente acceso a la clase derivada. –

0

Tienes que empezar a pensar en los términos de lo que estás tratando de modelar aquí.

La herencia pública solo se debe usar para modelar una relación "isa", p. Ej. un perro es un animal, un cuadrado es una forma, etc.

Eche un vistazo al libro Effective C++ de Scott Meyer para obtener un excelente ensayo sobre cómo deben interpretarse los diversos aspectos del diseño OO.

Editar: Olvidé decir que aunque las respuestas proporcionadas hasta ahora son técnicamente correctas, no creo que ninguna de ellas resuelva los problemas de lo que está tratando de modelar y ese es el quid de su problema.

HTH

aplausos,

Rob

0

encontré un ejemplo bueno y sencillo en el siguiente enlace. El artículo explica con un programa de ejemplo para calcular el área y el perímetro de un rectángulo. Puede verificarlo ... cheques

La herencia multinivel es una jerarquía de herencia en la que una clase derivada hereda de múltiples clases base. Leer más ..

http://www.mobihackman.in/2013/09/multiple-inheritance-example.html