Una función virtual es una función miembro que se declara dentro de una clase base y se redefine (anula) por una clase derivada. Cuando hace referencia a un objeto de clase derivada mediante un puntero o una referencia a la clase base, puede llamar a una función virtual para ese objeto y ejecutar la versión de la función 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 de función.
- Se utilizan principalmente para lograr el polimorfismo en tiempo de ejecución.
- Las funciones se declaran con una palabra clave virtual en la clase base.
- La resolución de la llamada de función se realiza en tiempo de ejecución.
Reglas para funciones virtuales
- Las funciones virtuales no pueden ser estáticas.
- Una función virtual puede ser una función amiga de otra clase.
- Se debe acceder a las funciones virtuales mediante un puntero o una referencia del tipo de clase base para lograr el polimorfismo en tiempo de ejecución.
- El prototipo de las funciones virtuales debe ser el mismo tanto en la clase base como en la clase derivada.
- 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 usa la versión de clase base de la función.
- Una clase puede tener un destructor virtual pero no puede tener un constructor virtual.
Comportamiento del tiempo de compilación (enlace anticipado) frente al tiempo de ejecución (enlace tardío) de las funciones virtuales
Considere el siguiente programa simple que muestra el comportamiento en tiempo de ejecución de las funciones virtuales.
CPP
// CPP program to illustrate // concept of Virtual Functions #include<iostream> using namespace std; class base { public: virtual void print() { cout << "print base class\n"; } void show() { cout << "show base class\n"; } }; class derived : public base { public: void print() { cout << "print derived class\n"; } void show() { cout << "show derived class\n"; } }; int main() { base *bptr; derived d; bptr = &d; // Virtual function, binded at runtime bptr->print(); // Non-virtual function, binded at compile time bptr->show(); return 0; }
Producción:
print derived class show base class
Explicación: el polimorfismo en tiempo de ejecución solo se logra a través de un puntero (o referencia) del tipo de clase base. Además, un puntero de clase base puede apuntar a los objetos de la clase base así 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 a la que apunta 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 una palabra clave virtual, por lo que se vinculará en tiempo de ejecución (la salida es una clase derivada de impresión ya que el puntero apunta al objeto de la clase derivada) y show() no es virtual, por lo que se vinculará durante el tiempo de compilación (la salida es mostrar la clase basecomo 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 la palabra clave virtual en la clase derivada, las funciones se consideran automáticamente como funciones virtuales en la clase derivada.
Funcionamiento de funciones virtuales (concepto de VTABLE y VPTR)
Como se discutió aquí , si una clase contiene una función virtual, entonces el compilador hace dos cosas.
- Si se crea un objeto de esa clase, se inserta un puntero virtual (VPTR) como miembro de datos de la clase para apuntar a VTABLE de esa clase. Para cada nuevo objeto creado, se inserta un nuevo puntero virtual como miembro de datos de esa clase.
- Independientemente de si el objeto se crea o no, la clase contiene como miembro una array 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:
CPP
// CPP program to illustrate // working of Virtual Functions #include<iostream> using namespace std; class base { public: void fun_1() { cout << "base-1\n"; } virtual void fun_2() { cout << "base-2\n"; } virtual void fun_3() { cout << "base-3\n"; } virtual void fun_4() { cout << "base-4\n"; } }; class derived : public base { public: void fun_1() { cout << "derived-1\n"; } void fun_2() { cout << "derived-2\n"; } void fun_4(int x) { cout << "derived-4\n"; } }; int main() { base *p; derived obj1; p = &obj1; // Early binding because fun1() is non-virtual // in base p->fun_1(); // Late binding (RTP) p->fun_2(); // Late binding (RTP) p->fun_3(); // Late binding (RTP) p->fun_4(); // Early binding but this function call is // illegal (produces error) because pointer // is of base type and function is of // derived class // p->fun_4(5); return 0; }
Producción:
base-1 derived-2 base-3 base-4
Explicación: Inicialmente, creamos un puntero de tipo clase base y lo inicializamos con la dirección del objeto de clase derivado. 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.
Se utiliza un concepto similar de enlace temprano y tardío como en el ejemplo anterior. Para la llamada a la función fun_1(), se llama a la versión de la función de la clase base, fun_2() se anula en la clase derivada, por lo que se llama a la versión de la clase derivada, fun_3() no se anula en la clase derivada y es una función virtual, por lo que 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 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 hace que sea más difícil para el compilador optimizar 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 averiguar desde dónde se llama a una función.
Este artículo es una contribución de Yash Singla . Si te gusta GeeksforGeeks y te gustaría contribuir, también puedes escribir un artículo usando write.geeksforgeeks.org o enviar tu artículo por correo a review-team@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.
Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.
Publicación traducida automáticamente
Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA