- Herencia virtual
-
En C++, herencia virtual es un tipo de herencia que solventa algunos de los problemas causados por la herencia múltiple (particularmente, el "problema del diamante") mediante la aclaración de la ambigüedad sobre qué miembros de clases padre usar. Es usada cuando la herencia está representando restricciones de un conjunto más que la composición de partes. Una clase base multi-heredada se denota como virtual con la palabra clave
virtual
.El problema
Considerando la siguiente jerarquía de clase.
class Animal { virtual void Come(); }; class Mamífero : public Animal { public: virtual Color GetColorPelo(); }; class AnimalConAlas : public Animal { public: virtual void Aletea(); }; // Un murciélago es un mamífero con alas class Murciélago : public Mamífero, public AnimalConAlas {};
Pero, ¿cómo
Come()
unmurciélago
? Como se ha declarado arriba, una llamada aMurciélago.Come()
es ambiguo. Debería llamarse aMurciélago.AnimalConAlas::Come()
o aMurciélago.Mamífero::Come()
. El problema es que la semántica de la herencia múltiple convencional no modela la realidad. En un sentido, unAnimal
esAnimal
sólo una vez; unMurciélago
es unMamífero
y unAnimalConAlas
, pero unMurciélago
es tanAnimal
por serMamífero
como por serAnimalConAlas
.Esta situación es llamada a veces herencia en diamante y es un problema que la herencia virtual intenta, en parte, solventar.
Representación de clases
Antes de seguir es útil tener en cuenta cómo se representan las clases en C++. Concretamente, la herencia es sólo cuestión de poner la clase padre y la clase hija una detrás de la otra en memoria. De esta forma Murciélago es realmente (Animal, Mamífero, Animal, AnimalConAlas, Murciélago), lo que hace que se duplique Animal, causando la ambigüedad.
Solución
Podemos redeclarar nuestras clases de la forma siguiente:
// Dos clases heredando virtualmente de Animal: class Mamífero : public virtual Animal { public: virtual Color GetColorPelo(); }; class AnimalConAlas : public virtual Animal { public: virtual void Aletea(); }; // Un murciélago sigue siendo un mamífero con alas class Murciélago : public Mamífero, public AnimalConAlas {};
Ahora la parte
Animal
deMurciélago::AnimalConAlas
es la misma que la usada enMurciélago::Mamífero
, que es como decir que unMurciélago
sólo tiene unAnimal
en su representación y por tanto llamar aMurciélago::Come()
no es ambiguo.Esto se implementa ofreciendo
Mamífero
yAnimalConAlas
con una vtable ya que, por ejemplo, el desplazamiento de memoria entre el comienzo de unMamífero
y de su parteAnimal
no se conoce hasta el momento de la ejecución del programa. De esta forma Murciélago pasa a ser (vtable*, Mamífero, vtable*, AnimalConAlas, Murciélago, Animal).Con dos punteros vtable (vtable*) por objeto, el tamaño del objeto aumenta en dos punteros, pero así sólo hay un Animal y no hay ambigüedad. Hay un puntero Animal por cada herencia que hereda virtualmente de Animal: Mamífero y AnimalConAlas. Todos los objetos del tipo Murciélago tendrán los mismos vtable*, pero cada objeto Murciélago contendrá un único objeto Animal propio. Si otra clase hereda de Mamífero, como Ardilla, la vtable* en el objeto Mamífero de Ardilla será diferente del vtable* en el objeto Mamífero de un Murciélago, aunque podrían ser esencialmente el mismo en el caso especial de que la parte Ardilla del objeto tuviera el mismo tamaño que la parte Murciélago, ya que la distancia desde la parte Mamífero hasta la parte Animal sería la misma. Las vtables no son realmente las mismas, pero la información esencial en ellas (la distancia), sí lo es.
Wikimedia foundation. 2010.