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
[editar]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()
un murciélago
? Como se ha declarado arriba, una llamada a Murciélago.Come()
es ambiguo. Debería llamarse a Murciélago.AnimalConAlas::Come()
o a Murciélago.Mamífero::Come()
. El problema es que la semántica de la herencia múltiple convencional no modela la realidad. En un sentido, un Animal
es Animal
solo una vez; un Murciélago
es un Mamífero
y un AnimalConAlas
, pero un Murciélago
es tan Animal
por ser Mamífero
como por ser AnimalConAlas
.
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
[editar]Antes de seguir es útil tener en cuenta cómo se representan las clases en C++. Concretamente, la herencia es solo 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
[editar]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
de Murciélago::AnimalConAlas
es la misma que la usada en Murciélago::Mamífero
, que es como decir que un Murciélago
solo tiene un Animal
en su representación y por tanto llamar a Murciélago::Come()
no es ambiguo.
Esto se implementa ofreciendo Mamífero
y AnimalConAlas
con una vtable ya que, por ejemplo, el desplazamiento de memoria entre el comienzo de un Mamífero
y de su parte Animal
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í solo 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.