C++
Herencia virtual en c++, consideraciones sobre los constructores de las clases bases virtuales
La herencia virtual es genial, nos soluciona el problema del diamante pero esta medida se puede volver un inconveniente cuando la incorporamos por inercia, cuando heredamos virtualmente sin preocuparnos si estamos heredando interfaces o bien una clase con sus métodos y atributos.
En este post veremos las implicaciones que tiene heredar virtualmente una clase que no sea una interfaz .
El problema del diamante
Cuando creo una clase suelo componerlo por interfaces (que no es lo mismo que clases abstractas que pueden tener estado), de esta manera mato tres pájaros de un tiro:
- En tiempo de compilación se lanzará un error si me olvido de implementar algún método virtual puro.
- Utilizar el polimorfismo y abstraer de esta manera la implementación, pudiendo utilizar patrones de diseño como la Factoría o Estrategia en nuestras soluciones.
- Una implicación de lo anterior, si creamos nuestra clase por composición, donde la firma son interfaces, la etapa de los tests no será una pesadilla.
Pero hay veces que algunas interfaces heredan de la misma interfaz y luego componemos nuestra clase de dichas interfaces. Si no hemos tenido la precaución de añadirlo como herencia virtual, al compilar se quejará diciendo que la resolución del método es ambigua.
error: request for member ‘Tocar’ is ambiguous
Problema del diamante
La herencia múltiple se presenta muchas veces cuando creamos la interfaz de una clase a partir de interfaces más pequeñas. Incorporando la clave virtual
en la herencia de la clase base de Der1
y Der2
se elimina la ambigüedad al crear sólamente un subobjeto de Base cuando instanciamos Instrumento
Incorporando la herencia virtual a lo loco
Imaginemos que empezamos a crear una clase a partir de una interfaz, y por inercia especificamos que todas nuestras herencias son virtuales como el ejemplo de abajo, en este caso, se llamará al constructor por defecto ya que la invocación de los constructores de las clases bases virtuales se deben realizar en la lista de inicialización de la clase más derivada.
Abuso de la herencia virtual
El propósito de la herencia virtual es asegurar que solamente creamos un objeto, para ello el compilador tiene unos mecanismos determinados como instanciar las clases bases virtuales antes de las demás. Por ello, si no tenemos precaución en instanciar Der1 en Der3 antes de Der2, el compilador utilizará el constructor por defecto.
De hecho solamente podemos invocar al constructor de clases intermedias si se heredan virtualmente, de otro modo, el compilar daría un error parecido a este:
error: type ‘Der1’ is not a direct or virtual base of ‘Der3’
Conclusión
Se puede realizar herencia virtual de clases con atributos pero tiene un gran inconveniente:
- La última clase derivada tiene que invocar al constructor de las clases bases virtuales que tienen atributos, por ende, tiene que conocer como se invoca y además si no queremos problemas, siempre se debe invocar de la misma forma (si estamos en el problema del diamante).
Sin embargo, esto no es un problema si seguimos las recomendaciones de heredar virtualmente solo interfaces, que al no tener atributos no necesitan un constructor por parámetros.
Un buen FAQ de la herencia multiple y virtual c++
Ploblema del diamente
Consideraciones de los constructores de las clases bases virtuales