Las funciones virtuales permiten implementar polimorfismo: la capacidad de manejar objetos de clases derivadas a través de punteros o referencias a la clase base. Para declarar una función virtual se utiliza la palabra clave virtual:
class Base { public: virtual void foo() { std::cout << "Base::foo"; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo"; } };
Llamar a foo() a través de un puntero de tipo Base* invocará la implementación en Derived, si apunta a un objeto de Derived.
Clase abstracta: contiene al menos una función virtual pura:
class Interface { public: virtual void process() = 0; // función virtual pura };
Herencia virtual resuelve el problema de la herencia diamante:
class A { }; class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { };
Esto garantiza que en el objeto D habrá una única instancia de la clase base A.
¿Cuál es la diferencia entre un destructor virtual y uno normal? ¿Es necesario hacer el destructor virtual si la clase está destinada a ser heredada?
Respuesta: Un destructor virtual garantiza que al eliminar a través de un puntero de tipo base se llamará al destructor de la clase derivada. Esto es importante para liberar correctamente los recursos.
class Base { public: virtual ~Base() {} };
Si el destructor no es virtual, al usar delete BasePtr;, en el objeto de la clase derivada solo se llamará al destructor de Base, y los recursos de los campos heredados no serán liberados.
Historia
En un gran sistema financiero, donde las distintas herramientas tenían una interfaz de clase común, no se declaró el destructor virtual. En uno de los derivados se utilizaba un recurso dinámico. Al eliminar el objeto a través de un puntero de tipo base, se produjo una fuga de memoria que solo se detectó en la fase de cargas industriales.
Historia
El equipo utilizó herencia múltiple sin aplicar herencia virtual. La clase D heredó A dos veces a través de clases intermedias. Esto condujo a la duplicación del estado y errores al acceder a los miembros de A. Se corrigió solo después de una auditoría con herramientas de análisis estático.
Historia
En un proyecto de desarrollo de plugins de registro, se utilizó una clase abstracta, pero no se hizo su destructor virtual. Al eliminar los plugins a través de un puntero a la interfaz, se observaron dependencias no evidentes y errores relacionados con destructores no invocados de los descendientes. El problema abarcaba el grupo de recursos y condujo a fugas de recursos.