Anuncios

Bienvenidos sean a este post, hoy veremos una palabra clave para los metodos.

Anuncios

Esta palabra se utiliza principalmente para los metodos en clase. Este permite que la establezcamos en la clase base y la redefinimos (override) en una clase heredera de esta. Para cuando referimos un objeto de la clase derivada usando un puntero o una referencia a la clase base, se puede llamar al metodo virtual para ese objeto y ejecutar la version del metodo de la clase derivada. Mencionemos algunas caracteristicas sobre la misma:

  • Esta nos asegura que se llama a la funcion correcta para un objeto, sin importar el tipo de referencia (o puntero) usado para llamar a la funcion
  • Son utilizados principalmente para el polimorfismo
  • Siempre se utilizan en la clase base
  • La resolucion del llamado de funcion se hace en ejecucion
Anuncios

Pero el uso de este tipo de metodos debe seguir las siguientes reglas:

  • Estas no pueden ser estaticas
  • Estas pueden ser funciones amigas de otras clases
  • Deben ser accedidas usando un puntero o referencia del tipo de la clase base para alcanzar el polimorfismo
  • El prototipo debe ser el mismo en la clase base y en la clase heredera
  • Siempre se definen en la clase base y se redefine en la clase heredera. Aunque esto no es obligatorio, dado que podemos usar a la de clase base
  • Puede tener un destructor virtual pero no un constructor virtual
Anuncios

Veamos un ejemplo para ver como trabaja. Para ello analicemos el siguiente codigo:

#include <iostream>

class Base
{
public:
        virtual void imprimir();
        void mostrar();
};

class Heredera: public Base
{
public:
        void imprimir();
        void mostrar();
};

void Base::imprimir() { std::cout << "imprimir clase base\n"; }
void Base::mostrar() { std::cout << "mostrar clase base\n"; }

void Heredera::imprimir() { std::cout << "imprimir clase heredera\n"; }
void Heredera::mostrar() { std::cout << "mostrar clase heredera\n"; }

int main()
{
        Base* bp;
        Heredera h;
        bp = &h;

        bp->imprimir();
        bp->mostrar();

        return 0;
}
Anuncios
Anuncios

Primero definiremos una clase llamada Base y esta contendra dos metodos, imprimir y mostrar, de los cuales haremos virtual a imprimir. Estos son prototipos de los mismos y los definiremos en un momento. Luego tenemos la clase heredera de la anterior donde redefiniremos los metodos anteriores. Al igual que en el caso anterior son solo prototipos. Despues de ambas clases tenemos las definiciones de cada metodo. En el caso de la virtual, solo se uso en la clase y no fue necesario usarlo nuevamente. En cada metodo mostraremos un mensaje indicando cual es y desde donde proviene. En el main, primero crearemos un puntero de la clase Base. Luego un objeto de Heredera y pasaremos como referencia este objeto en el puntero. Para luego usar el puntero y llamar a los metodos. Compilemos y veamos como es su salida:

$ ./virtual
imprimir clase heredera
mostrar clase base
$
Anuncios

Primera curiosidad, al llamar al metodo imprimir utilizo en el de la clase heredera. Recuerden que si bien el puntero es de Base este apunta a un objeto de tipo Heredera. En el segundo caso, si llamo al metodo de Base. A esto nos referiamos lo mencionado al inicio. Cuando se utilice a virtual en un metodo, este llamara al de la clase donde apunta y no del tipo del puntero. Como podemos ver en el segundo caso. Tomemos el codigo anterior y realicemos la siguiente modificacion:

#include <iostream>

class Base
{
public:
        void func_1() { std::cout << "base-1\n"; }
        virtual void func_2() { std::cout << "base-2\n"; }
        virtual void func_3() { std::cout << "base-3\n"; }
        virtual void func_4() { std::cout << "base-4\n"; }
};

class Heredera: public Base
{
public:
        void func_1() { std::cout << "heredero-1\n"; }
        void func_2() { std::cout << "heredero-2\n"; }
        void func_4(int x) { std::cout << "heredero-4\n"; }
};

int main()
{
        Base* bp;
        Heredera h;
        bp = &h;

        bp->func_1();
        bp->func_2();
        bp->func_3();
        bp->func_4();

        return 0;
}
Anuncios
Anuncios

Principalmente modificamos las dos clases donde eliminamos los metodos anteriores y agregamos unos nuevos. Analicemos la primer clase, aqui tenemos cuatro metodos donde el primero no sera virtual pero el resto si. En cada caso, mostraremos un mensaje indicando desde cual clase provienen y a que metodo representan. En la otra clase, redefinimos a dos metodos donde mostraremos lo mismo. Es decir, la clase donde proviene y el metodo desde donde es llamado. Pero tenemos un nuevo metodo con el nombre de uno de los metodos de la clase anterior pero al tener un argumento esto no lo redefine sino que lo sobrecarga. En el main tenemos la misma creacion de puntero y objetos que hicimos anteriorrmente. Y nuevamente volvemos a usar el puntero para llamar a cada metodo. Compilemos y veamos como es la salida:

$ ./virtual
base-1
heredero-2
base-3
base-4
$
Anuncios
Anuncios

Cuando usamos metodos virtuales se crean dos elementos para poder manipularlos. Estos son VTABLE y VPTR. Si se crea un objeto de la clase como vimos hasta ahora, este genera un VPTR (Puntero Virtual) se ingresa como miembro de la clase que apunta a la VTABLE de esa clase. Y por cada objeto creado se repite este mismo procedimiento. Mas alla de la creacion del objeto o no, la clase contiene como miembro un array estatico de punteros de funciones. En cada posicion almacenamos la direccion de memoria de cada metodo virtual contenido en la clase.

Anuncios
Anuncios

Por lo tanto, al momento de crear el puntero de la clase e iniciarlo con la direccion del objeto de la otra clase cuando lo compilemos se creara un puntero como un miembro de la clase conteniendo la direccion de la VTABLE de la clase heredera. Volviendo a nuestro codigo, si lo ejecutan nos devolvera un solo metodo de la clase Heredera. Esto es asi porque el primer metodo no es virtual, por lo tanto no se ingresa a la tabla, el tercero no existe y por lo tanto no lo redefine, y el ultimo no es una redefinicion sino una sobrecarga. Como no aplican las reglas que mencionamos al inicio, no se cargan en la tabla y por lo tanto utilizara los metodos de la clase Base. Vamos a ver una curiosidad, para ello busquen la siguiente linea en el codigo anterior:

bp->func_4();
Anuncios

Y lo modificaremos de la siguiente manera:

bp->func_4(5);
Anuncios

En este caso, vamos a utilizar el metodo que agregamos en la segunda clase. Si lo compilan devolvera el siguiente error:

$ g++ virtual.cpp -o virtual
virtual.cpp: In function ‘int main()’:
virtual.cpp:29:19: error: no matching function for call to ‘Base::func_4(int)’
   29 |         bp->func_4(5);
      |         ~~~~~~~~~~^~~
virtual.cpp:9:22: note: candidate: ‘virtual void Base::func_4()’
    9 |         virtual void func_4() { std::cout << "base-4\n"; }
      |                      ^~~~~~
virtual.cpp:9:22: note:   candidate expects 0 arguments, 1 provided
$
Anuncios

Como dijimos, al ser virtual se agrega en la tabla pero como no la redefinimos no puede encontrarla. Por esta razon, nos dice que no tiene ninguna coincidencia y nos muestra el posible candidato. Pero este no espera ningun argumento y por lo tanto no puede usarlo. Esto es simplemente para ver que sucede al no cumplir con las reglas informadas. Antes de terminar, vamos a comentar dos problemas que nos pueden surgir:

  • Este mecanismo ralentiza nuestro codigo, esto es porque agregamos mas procedimientos al momento de compilarlo y no sabra cuales metodos seran llamados al momento de ejecucion
  • Tambien nos dificultara la depuracion de nuestro codigo, porque nos puede dificultar ver desde donde se llama a la funcion
Anuncios

En resumen, hoy hemos visto a virtual, que es, para que sirve, como se utiliza, unos ejemplos para ver los distintos tipos de uso, asi como un ejemplo donde no se respetan las reglas de uso. 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.

Anuncios
pp258

Donatión

It’s for site maintenance, thanks!

$1.50