Herencia virtual es un C ++ técnica que asegura que sólo una copia de una clase base ' variables miembro s se hereda por las clases nieto derivada. Sin herencia virtual, si dos clases B
y C
heredan de una clase A
, y una clase D
hereda de ambas B
y C
, entonces D
contendrá dos copias de A
las variables miembro de: una vía B
y otra vía C
. Estos serán accesibles de forma independiente, utilizando resolución de alcance .
En cambio, si las clases B
y C
heredan virtualmente de la clase A
, los objetos de la clase D
contendrán solo un conjunto de las variables miembro de la clase A
.
Esta característica es más útil para la herencia múltiple , ya que hace que la base virtual sea un subobjeto común para la clase derivada y todas las clases que se derivan de ella. Esto se puede usar para evitar el problema del diamante al aclarar la ambigüedad sobre qué clase de ancestro usar, ya que desde la perspectiva de la clase derivada ( D
en el ejemplo anterior) la base virtual ( A
) actúa como si fuera la clase base directa de D
, no una clase derivada indirectamente a través de una base ( B
o C
). [1] [2]
Se utiliza cuando la herencia representa la restricción de un conjunto en lugar de la composición de partes. En C ++, una clase base destinada a ser común en toda la jerarquía se denota como virtual con la virtual
palabra clave .
Considere la siguiente jerarquía de clases.
struct Animal { virtual ~ Animal () = predeterminado ; comer vacío virtual () {} }; struct Mammal : Animal { virtual void Breathe () {} };struct WingedAnimal : Animal { virtual void Flap () {} };// Un murciélago es una estructura de mamífero alado Bat : Mammal , WingedAnimal {};Murciélago murciélago ;
Como se declaró anteriormente, una llamada a bat.Eat
es ambigua porque hay dos Animal
clases base (indirectas) Bat
, por lo que cualquier Bat
objeto tiene dos Animal
subobjetos de clase base diferentes . Entonces, un intento de vincular directamente una referencia al Animal
subobjeto de un Bat
objeto fallaría, ya que la vinculación es inherentemente ambigua:
Bat b ; Animal & a = b ; // error: ¿en qué subobjeto Animal debería lanzar un murciélago, // un Mammal :: Animal o un WingedAnimal :: Animal?
Para eliminar la ambigüedad, uno tendría que convertir explícitamente bat
a cualquiera de los subobjetos de clase base:
Bat b ; Animal & mamífero = static_cast < Mamífero &> ( b ); Animal & winged = static_cast < WingedAnimal &> ( b );
Para llamar Eat
, se necesita la misma desambiguación o calificación explícita: static_cast
o static_cast
o alternativamente bat.Mammal::Eat()
y bat.WingedAnimal::Eat()
. La calificación explícita no solo utiliza una sintaxis uniforme y más fácil para punteros y objetos, sino que también permite el envío estático, por lo que podría decirse que sería el método preferible.
En este caso, la doble herencia de Animal
probablemente no sea deseada, ya que queremos modelar que la relación ( Bat
es an Animal
) existe solo una vez; que a Bat
es un Mammal
y es un WingedAnimal
no implica que sea un Animal
doble: una Animal
clase base corresponde a un contrato que Bat
implementa (la relación "es un" anterior realmente significa " implementa los requisitos de "), y a Bat
solo implementa el Animal
contrato una vez . El significado del mundo real de " es una sola vez" es que Bat
debería tener solo una forma de implementación Eat
, no dos formas diferentes, dependiendo de si la Mammal
vista de la Bat
es comer o la WingedAnimal
vista de la Bat
. (En el primer ejemplo de código, vemos que Eat
no se anula en ninguno de los dos Mammal
o WingedAnimal
, por lo que los dos Animal
subobjetos se comportarán de la misma manera, pero este es solo un caso degenerado, y eso no hace una diferencia desde el punto de vista de C ++).
Esta situación a veces se denomina herencia de diamantes (consulte el problema de los diamantes ) porque el diagrama de herencia tiene la forma de un diamante. La herencia virtual puede ayudar a resolver este problema.
La solución
Podemos volver a declarar nuestras clases de la siguiente manera:
struct Animal { virtual ~ Animal () = predeterminado ; comer vacío virtual () {} }; // Dos clases que heredan virtualmente Animal: struct Mammal : virtual Animal { virtual void Breathe () {} };struct WingedAnimal : Animal virtual { Flap vacío virtual () {} }; // Un murciélago sigue siendo una estructura de mamífero alado Bat : Mammal , WingedAnimal {};
La Animal
parte de Bat::WingedAnimal
es ahora la misma Animal
instancia que la utilizada por Bat::Mammal
, lo que quiere decir que a Bat
solo tiene una Animal
instancia compartida en su representación y, por lo tanto, una llamada a Bat::Eat
es inequívoca. Además, una conversión directa de Bat
a Animal
tampoco es ambigua, ahora que solo existe una Animal
instancia a la que se Bat
podría convertir a.
La capacidad de compartir una sola instancia del Animal
padre entre Mammal
y WingedAnimal
se habilita registrando el desplazamiento de memoria entre los miembros Mammal
o WingedAnimal
y los de la base Animal
dentro de la clase derivada. Sin embargo, esto puede compensar en el caso general sólo es conocido en tiempo de ejecución, por lo que Bat
debe convertirse en ( vpointer
, Mammal
, vpointer
, WingedAnimal
, Bat
, Animal
). Hay dos punteros vtable , uno por jerarquía de herencia que hereda virtualmente Animal
. En este ejemplo, uno para Mammal
y otro para WingedAnimal
. Por lo tanto, el tamaño del objeto ha aumentado en dos punteros, pero ahora solo hay uno Animal
y no hay ambigüedad. Todos los objetos de tipo Bat
usarán los mismos vpointers, pero cada Bat
objeto contendrá su propio Animal
objeto único . Si otra clase hereda de Mammal
, como Squirrel
, entonces el vpointer en la Mammal
parte de Squirrel
generalmente será diferente al vpointer en la Mammal
parte de, Bat
aunque pueden ser iguales si la Squirrel
clase tiene el mismo tamaño que Bat
.
Referencias
- ^ Milea, Andrei. "Resolver el problema del diamante con herencia virtual" . Cprogramming.com . Consultado el 8 de marzo de 2010 .
Uno de los problemas que surge debido a la herencia múltiple es el problema del diamante. Bjarne Stroustrup (el creador de C ++) da una ilustración clásica de esto en el siguiente ejemplo:
- ^ McArdell, Ralph (14 de febrero de 2004). "C ++ / ¿Qué es la herencia virtual?" . Todos los expertos . Archivado desde el original el 10 de enero de 2010 . Consultado el 8 de marzo de 2010 .
Esto es algo que encontrará que puede ser necesario si está utilizando herencia múltiple. En ese caso, es posible que una clase se derive de otras clases que tienen la misma clase base. En tales casos, sin herencia virtual, sus objetos contendrán más de un subobjeto del tipo base que comparten las clases base. Si éste es el efecto requerido depende de las circunstancias. Si no es así, puede usar la herencia virtual especificando clases base virtuales para aquellos tipos base para los cuales un objeto completo solo debe contener un subobjeto de clase base.