logo

Función virtual en C++

Una función virtual (también conocida como métodos virtuales) es una función miembro que se declara dentro de una clase base y es redefinida (anulada) por una clase derivada. Cuando hace referencia a un objeto de clase derivada utilizando un puntero o una referencia a la clase base, puede llamar a una función virtual para ese objeto y ejecutar la versión del método de la clase derivada.

  • Las funciones virtuales garantizan que se llame a la función correcta para un objeto, independientemente del tipo de referencia (o puntero) utilizado para la llamada a la función.
  • Se utilizan principalmente para lograr polimorfismo en tiempo de ejecución.
  • Las funciones se declaran con una virtual palabra clave en una clase base.
  • La resolución de una llamada a función se realiza en tiempo de ejecución.

Reglas para funciones virtuales

Las reglas para las funciones virtuales en C++ son las siguientes:



  1. Las funciones virtuales no pueden ser estáticas.
  2. Una función virtual puede ser una función amiga de otra clase.
  3. Se debe acceder a las funciones virtuales mediante un puntero o referencia de tipo de clase base para lograr polimorfismo en tiempo de ejecución.
  4. El prototipo de funciones virtuales debe ser el mismo tanto en la clase base como en la clase derivada.
  5. Siempre se definen en la clase base y se anulan en una clase derivada. No es obligatorio que la clase derivada anule (o redefina la función virtual); en ese caso, se utiliza la versión de clase base de la función.
  6. Una clase puede tener un destructor virtual pero no puede tener un constructor virtual.

Tiempo de compilación (enlace temprano) Comportamiento del tiempo de ejecución VS (enlace tardío) de funciones virtuales

Considere el siguiente programa simple que muestra el comportamiento de ejecución de funciones virtuales.

C++








matriz de bytes a cadena
// C++ program to illustrate> // concept of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >virtual> void> print() { cout <<>'print base class '>; }> >void> show() { cout <<>'show base class '>; }> };> class> derived :>public> base {> public>:> >void> print() { cout <<>'print derived class '>; }> >void> show() { cout <<>'show derived class '>; }> };> int> main()> {> >base* bptr;> >derived d;> >bptr = &d;> >// Virtual function, binded at runtime> >bptr->imprimir();> >// Non-virtual function, binded at compile time> >bptr->mostrar();> >return> 0;> }>

>

>

Producción

print derived class show base class>

Explicación: El polimorfismo en tiempo de ejecución se logra únicamente mediante un puntero (o referencia) del tipo de clase base. Además, un puntero de clase base puede apuntar tanto a los objetos de la clase base como a los objetos de la clase derivada. En el código anterior, el puntero de clase base 'bptr' contiene la dirección del objeto 'd' de la clase derivada.

El enlace tardío (tiempo de ejecución) se realiza de acuerdo con el contenido del puntero (es decir, la ubicación señalada por el puntero) y el enlace temprano (tiempo de compilación) se realiza de acuerdo con el tipo de puntero, ya que la función print() se declara con el virtual palabra clave para que se vincule en tiempo de ejecución (la salida es clase derivada de impresión ya que el puntero apunta al objeto de la clase derivada) y show() no es virtual, por lo que estará vinculado durante el tiempo de compilación (la salida es mostrar clase base ya que el puntero es de tipo base).

Nota: Si hemos creado una función virtual en la clase base y se anula en la clase derivada, entonces no necesitamos una palabra clave virtual en la clase derivada, las funciones se consideran automáticamente funciones virtuales en la clase derivada.

Trabajo de Funciones Virtuales (concepto de VTABLE y VPTR)

Como se analiza aquí, si una clase contiene una función virtual, entonces el compilador hace dos cosas.

  1. Si se crea un objeto de esa clase, entonces un puntero virtual (VPTR) se inserta como miembro de datos de la clase para apuntar a la VTABLE de esa clase. Para cada nuevo objeto creado, se inserta un nuevo puntero virtual como miembro de datos de esa clase.
  2. Independientemente de si el objeto se crea o no, la clase contiene como miembro una matriz estática de punteros de función llamada VTABLE . Las celdas de esta tabla almacenan la dirección de cada función virtual contenida en esa clase.

Considere el siguiente ejemplo:

convertir cadena a int

puntero virtual y tabla virtual

C++




// C++ program to illustrate> // working of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >void> fun_1() { cout <<>'base-1 '>; }> >virtual> void> fun_2() { cout <<>'base-2 '>; }> >virtual> void> fun_3() { cout <<>'base-3 '>; }> >virtual> void> fun_4() { cout <<>'base-4 '>; }> };> class> derived :>public> base {> public>:> >void> fun_1() { cout <<>'derived-1 '>; }> >void> fun_2() { cout <<>'derived-2 '>; }> >void> fun_4(>int> x) { cout <<>'derived-4 '>; }> };> int> main()> {> >base* p;> >derived obj1;> >p = &obj1;> >// Early binding because fun1() is non-virtual> >// in base> >p->divertido_1();> >// Late binding (RTP)> >p->divertido_2();> >// Late binding (RTP)> >p->divertido_3();> >// Late binding (RTP)> >p->divertido_4();> >// Early binding but this function call is> >// illegal (produces error) because pointer> >// is of base type and function is of> >// derived class> >// p->divertido_4(5);> >return> 0;> }>

tira de pitón
>

>

Producción

base-1 derived-2 base-3 base-4>

Explicación: Inicialmente, creamos un puntero del tipo clase base y lo inicializamos con la dirección del objeto de clase derivada. Cuando creamos un objeto de la clase derivada, el compilador crea un puntero como miembro de datos de la clase que contiene la dirección de VTABLE de la clase derivada.

Un concepto similar de Encuadernación tardía y anticipada se utiliza como en el ejemplo anterior. Para la llamada a la función fun_1(), se llama a la versión de clase base de la función, fun_2() se anula en la clase derivada, por lo que se llama a la versión de clase derivada, fun_3() no se anula en la clase derivada y es una función virtual entonces se llama a la versión de la clase base, de manera similar, fun_4() no se anula, por lo que se llama a la versión de la clase base.

Nota: fun_4(int) en la clase derivada es diferente de la función virtual fun_4() en la clase base ya que los prototipos de ambas funciones son diferentes.

Limitaciones de las funciones virtuales

    Más lento: la llamada a la función tarda un poco más debido al mecanismo virtual y dificulta la optimización para el compilador porque no sabe exactamente qué función se llamará en el momento de la compilación. Difícil de depurar: en un sistema complejo, las funciones virtuales pueden hacer que sea un poco más difícil determinar desde dónde se llama una función.