Bienvenidos sean a este post, hoy veremos otra posibilidad que nos brinda la herencia.
En este post vimos como es la herencia entre clases. Como se pueden compartir sus propiedades y metodos, como tambien negarlos, y un nuevo estado donde lo prohibe para las instancias de una clase pero es publico para las clases herederas. Y otra habilidad propia de este lenguaje y que ningun lenguaje derivado de este, que yo conozca, posee como es la herencia multiple directa o polimorfismo. Si bien, en lenguajes como Java o C# (el Java de Microsoft) poseen una herencia multiple, esta no es directa y se realiza mediante otro elemento como son las interfaces, aqui conocidas como virtuales puras de la cual hablamos en este post, y luego debemos definir todos los metodos en estas. En cambio, en C++ es muchisimo mas practico y una de las claves de por que al dia de hoy sigue siendo uno de los mas utilizados. Primero veamos como es su sintaxis:
class nombre : public clase_1, public clase_2,..., public clase_N
{
... instrucciones ...
}
Es muy similar a lo que vimos como hacerla heredera pero podemos pasarles mas clases para que sean herederas de las otras. Por cada clase que le pasemos debemos agregar el modificador public. El resto seguira siendo lo mismo, donde podemos acceder a todas las propiedades y los metodos de las clases madres, siempre y cuando sean public o protected. Tambien podemos redefinir los metodos asi como tambien agregar nuevos, tal como vimos en este post. Antes de ver el concepto de la herencia multiple vamos a establecer el codigo base que usaremos:
#include <iostream>
class Caballo
{
public:
void Galopar() { std::cout << "Galopando\n"; }
virtual void Volar() {
std::cout << "No puedo volar\n";
}
private:
int suEdad;
};
class Pegaso : public Caballo
{
public:
void Volar() { std::cout << "Puedo volar\n"; }
};
const int cantidad = 5;
int main()
{
Caballo* rancho[cantidad];
Caballo* pCaballo;
int opcion, i;
for(i = 0; i < cantidad; i++)
{
std::cout << "(1) Caballo (2) Pegaso: ";
std::cin >> opcion;
switch(opcion)
{
case "1":
pCaballo = new Caballo;
break;
case "2":
pCaballo = new Pegaso;
break;
}
rancho[i] = pCaballo;
}
std::cout << "***************" << std::endl;
for(i = 0; i < cantidad; i++)
{
rancho[i]->Volar();
delete rancho[i];
}
return 0;
}
En este tendremos dos clases, la primera sera Caballo y sera nuestra clase base. En ella tendremos dos metodos, la primera sera para indicar cuando el caballo esta galopando y la segunda sera para la accion de volar pero este indicara que no puede hacerlo. Este segundo metodo le aplicaremos a virtual para poder diferenciarlo entre este y el heredero, tal como vimos en este post. Luego tendremos la clase heredera de la anterior que sera un pegaso, este si puede volar y por lo tanto debemos redefinirlo y en este indicaremos que si puede volar. Ya tenemos ambas clases definidas, lo siguiente es establecer una constante que usaremos para indicar la cantidad de animales que usaremos.
En el main creamos un puntero de tipo Caballo con el nombre de rancho. Este sera un array donde almacenaremos los distintos objetos creados de Caballo y Pegaso. El limite sera establecido por la constante anterior. Despues tenemos un puntero que sera para almacenar para cada objeto que sea creado de las clases anteriores. Para luego declarar dos variables que usaremos para crear los objetos, opcion, y otro para el bucle. Lo siguiente es un bucle donde preguntaremos para ingresar una opcion y asi poder crear un objeto de alguna de las dos clases. El switch toma el valor que ingresemos, en base a este creara un objeto y lo asignara al puntero creado anteriormente. Y cada objeto creado sera asignado a una posicion del array. Una vez terminado el ciclo, mostraremos una linea de asteriscos para crear una separacion.
Seguido a esto, tenemos un bucle que pasara por cada objeto almacenado en el array y de cada uno llamara al metodo Volar. Esto nos mostrara el mensaje correspondiente a cada uno. Y luego de eso iremos eliminando cada uno de los mismos. Con todo esto comentado, compilemos y veamos como es trabaja:
$ ./multiple
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 2
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 2
(1) Caballo (2) Pegaso: 1
***************
No puedo volar
Puedo volar
No puedo volar
Puedo volar
No puedo volar
$
Como pueden ver tenemos un codigo donde almacenamos distintos tipos de animales, cada uno con sus propias caracteristicas. Pero esto es una simple implementacion de una herencia con virtual para poder utilizar el metodo correcto. Tomemos el codigo anterior y realicemos la siguiente modificacion:
#include <iostream>
class Caballo
{
public:
void Galopar() const { std::cout << "Galopando\n"; }
virtual void Relinchar() const {
std::cout << "Yihii. ";
}
private:
int suEdad;
};
class Ave
{
public:
virtual void Gorjear() const {
std::cout << "Groak. ";
}
virtual void Volar() const {
std::cout << "Puedo volar\n";
}
private:
int suPeso;
};
class Pegaso : public Caballo, public Ave
{
public:
void Gorjear() const { Relinchar(); }
};
const int cantidad = 3;
int main()
{
Caballo* rancho[cantidad];
Ave* pajarera[cantidad];
Caballo* pCaballo;
Ave* pAve;
int opcion, i;
for(i = 0; i < cantidad; i++)
{
std::cout << "(1) Caballo (2) Pegaso: ";
std::cin >> opcion;
switch(opcion)
{
case 1:
pCaballo = new Caballo;
break;
case 2:
pCaballo = new Pegaso;
break;
}
rancho[i] = pCaballo;
}
for(i = 0; i < cantidad; i++)
{
std::cout << "(1) Ave (2) Pegaso: ";
std::cin >> opcion;
switch(opcion)
{
case 1:
pAve = new Ave;
break;
case 2:
pAve = new Pegaso;
break;
}
pajarera[i] = pAve;
}
std::cout << "***************" << std::endl;
std::cout << "Rancho:\n";
for(i = 0; i < cantidad; i++)
{
Pegaso *pPeg = dynamic_cast < Pegaso* > (rancho[i]);;
if (pPeg) {
pPeg->Gorjear();
pPeg->Volar();
} else {
rancho[i]->Relinchar();
std::cout << "Solo soy un caballo\n";
}
delete rancho[i];
}
std::cout << "Pajarera:\n";
for(i = 0; i < cantidad; i++)
{
pajarera[i]->Gorjear();
pajarera[i]->Volar();
}
return 0;
}
Tenemos las dos clases anteriores, con algunos cambios, y una nueva. Comencemos con la primera que es Caballo donde mantenemos al metodo Galopar pero tenemos uno nuevo virtual llamado Relinchar y eliminamos al metodo Volar. Seguimos manteniendo a la propiedad privada suEdad. Ahora tenemos a la nueva propiedad denominada como Ave. Esta poseera dos metodos virtuales siendo el primero llamado Gorjear y el siguiente es Volar. Este es el mismo metodo que teniamos anteriorrmente en Caballo pero ahora lo heredaremos desde esta. En la parte privada tenemos una propiedad llamada suPeso. La ultima clase es Pegaso pero esta vez la haremos heredera tanto de Caballo como de Ave. Y en esta solo redefiniremos a Gorjear, donde llamaremos a Relinchar. Esto es asi porque Pegaso es un Caballo y no un Ave. Para las otras usaremos las heredadas. Modificamos el valor de cantidad para hacerla un poco mas corta.
En el main seguimos teniendo un array de tipo Caballo y el puntero de tipo Caballo. Pero tambien creamos un nuevo array llamado pajarera de tipo Ave y un puntero de tipo Ave. Mantenemos la declarracion de las variables para el ingreso de las opciones y el bucle. Seguimos teniendo el mismo bucle para almacenar a caballo o pegaso pero esta vez nos preguntara solo tres veces. Luego tenemos un ciclo que es igual pero esta vez almacenaremos los objetos en la pajarera y seran de tipo Ave o Pegaso. Si lo obsevan es exactamente lo mismo. Y luego tenemos un pequeño separador mostrando asteriscos.
Lo siguiente son dos bucles donde primero mostraremos todos los valores ingresados en rancho. Pero aqui agregaremos una particularidad como dynamic_cast. Esta permite establecer de manera dinamica si el tipo de dato es coincidente con el recibido. Tomemos este caso, vamos a crear un nuevo puntero. Este solo sera creado si el tipo recibido de la posicion del array es coincidente con el informado en la funcion. En nuestro caso sera Pegaso, si recibe otro omite la creacion del mismo. Lo siguiente es un condicional donde verifica si este puntero existe. En caso de ser verdad, llama a los dos metodos disponibles de esta clase. En caso contrario, consideramos que es del tipo Caballo y por lo tanto llamaremos al metodo Relinchar pero luego mostraremos un mensaje indicando que es un caballo. Aqui tambien eliminaremos cada dato que haya sido procesado. El siguiente bucle devuelve todos los datos almacenados en pajarera. En este caso, simplemente llamaremos a los metodos disponibles en Ave y Pegaso. Compilemos y veamos como trabaja:
$ ./multiple
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 2
(1) Caballo (2) Pegaso: 1
(1) Ave (2) Pegaso: 2
(1) Ave (2) Pegaso: 1
(1) Ave (2) Pegaso: 1
***************
Rancho:
Yihii. Solo soy un caballo
Yihii. Puedo volar
Yihii. Solo soy un caballo
Pajarera:
Yihii. Puedo volar
Groak. Puedo volar
Groak. Puedo volar
$
Ya vimos como aplicar una herencia multiple y como el ultimo heredero accede a todos los metodos corrrectamente. Pero aun nos queda un tema mas. Para ello debemos tomar el codigo anterior y lo modificaremos de la siguiente manera:
#include <iostream>
class Caballo
{
public:
Caballo(){}
Caballo(int edad): suEdad(edad) {
std::cout << "Constructor de Cabaallo\n";
}
virtual ~Caballo() { std::cout << "Destructor de Caballo\n"; }
void Galopar() { std::cout << "Galopando\n"; }
virtual void Relinchar() const {
std::cout << "Yihii. ";
}
virtual int GetEdad() const { return suEdad; }
private:
int suEdad;
};
class Ave
{
public:
virtual void Gorjear() const {
std::cout << "Groak. ";
}
virtual void Volar() const {
std::cout << "Puedo volar\n";
}
virtual int GetEdad() const { return suEdad; }
private:
int suEdad;
int suPeso;
};
class Pegaso : public Caballo, public Ave
{
public:
Pegaso(int edad): Caballo(edad) {
std::cout << "Constructor de Pegaso." << std::endl;
}
~Pegaso() { std::cout << "Destructor de Pegaso" << std::endl; }
void Gorjear() const { Relinchar(); }
};
int main()
{
Pegaso *pPeg = new Pegaso(7);
pPeg->Gorjear();
pPeg->Volar();
std::cout << "Edad: " << pPeg->GetEdad() << std::endl;
delete pPeg;
return 0;
}
Los principales son en el main pero tambien agregamos algunas cosas mas. Comencemos con el primer cambio. Este lo hicimos en la clase Caballo donde sobrecargamos al constructor y al destructor. En el caso del constructor lo usamos para iniciar la propiedad suEdad e indicar cuando fue ejecutado. En el destructor agregamos el mensaje para indicar cuando es invocado. Tambien agregamos un nuevo metodo para obtener el valor de suEdad. La siguiente modificacion es en la clase Ave donde agregamos una propiedad para la edad. Tambien agregamos un metodo para obtener ese valor. Si lo observan es exactamente igual a lo aplicado en Caballo. La siguiente modificacion es en la clase Pegaso donde agregamos un constructor y un destructor. El constructor lo usamos para asignar un valor a la edad heredada de Caballo pero para ello debemos usar el constructor que creamos anteriormente y mostraremos un mensaje para indicar cuando es llamado. El destructor solamente mostrara un mensaje cuando sea invocado.
En el main eliminamos todo el contenido que usamos para ingresar datos y ahora solamente creamos un objeto del tipo Pegaso. En este llamaremos a los metodos Gorjear y Volar. Asi como tambien el metodo para obtener la edad que le asignamos. Para finalmente eliminar el objeto creado. Si lo compilamos, sucedera lo siguiente:
$ g++ multiple.cpp -o multiple
multiple.cpp: In function ‘int main()’:
multiple.cpp:48:40: error: request for member ‘GetEdad’ is ambiguous
48 | std::cout << "Edad: " << pPeg->GetEdad() << std::endl;
| ^~~~~~~
multiple.cpp:27:21: note: candidates are: ‘virtual int Ave::GetEdad() const’
27 | virtual int GetEdad() const { return suEdad; }
| ^~~~~~~
multiple.cpp:14:21: note: ‘virtual int Caballo::GetEdad() const’
14 | virtual int GetEdad() const { return suEdad; }
| ^~~~~~~
$
Fallo en la compilacion porque como lo indica tenemos una ambigüedad. Esto es asi porque tenemos dos metodos iguales que son heredados por la misma clase y no le decimos cual debemos utilizar. Veamos la primera solucion, para ello busquen la siguiente linea:
std::cout << "Edad: " << pPeg->GetEdad() << std::endl;
La modificaremos de la siguiente manera:
std::cout << "Edad: " << pPeg->Caballo::GetEdad() << std::endl;
Este funcionara porque como nos indicaba en el error le especificamos cual de los dos vamos a usar. Pero puede suceder que no queremos usarlo de esta manera. Para ello en lugar de hacer esta modificacion podemos agregar al siguiente metodo en la clase Pegaso:
int GetEdad() const { return Caballo::GetEdad(); }
Es una redefinicion de este metodo y desde este llamaremos al metodo de la clase Base. Estas dos posibles soluciones nos permiten evitar caer en la ambigüedad a la hora de heredar a traves de multiples clases. Siendo uno de los problemas mas importantes a la hora de aplicar el polimorfismo.
En resumen, hoy hemos visto polimorfismo, tambien conocido como herencia multiples, que es, como se aplica, una serie de ejemplos para ver todos los posibles, asi como tambien un problema de ambigüedad que puede surgir entre los distintos metodos heredados. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donatión
It’s for site maintenance, thanks!
$1.50
