Cuándo se necesitan destructores virtuales en C++: análisis en 2025

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Destructores virtuales en C++: por qué siguen siendo relevantes (y cuándo puedes olvidarte)

\n\n

Si alguna vez has programado en C++ y te has topado con la herencia, seguramente has visto esta pregunta en Stack Overflow con decenas de miles de visitas: «When to use virtual destructors?». En 2025, el tema no ha perdido relevancia: las fugas de memoria por omitir virtual siguen causando estragos en producción. Pero también hay una cara opuesta: añadir virtual sin pensar en todos lados tampoco es una buena práctica.

\n\n

Aclaremos esto de una vez por todas: cuándo un destructor virtual es obligatorio, cuándo es solo una buena práctica y cuándo su presencia indica una arquitectura deficiente. Allá vamos.

\n\n

Respuesta corta (para quienes tienen prisa)

\n\n

Un destructor virtual es necesario si y solo si vas a eliminar un objeto de una clase derivada a través de un puntero a la clase base. Si no existe ese escenario en tu código, no se requiere virtual. Pero hay matices que analizaremos a continuación.

\n\n

¿Por qué es un problema? Anatomía de una fuga

\n\n

Veamos el antipatrón clásico que rompe el código de los principiantes (y no solo de ellos).

\n\n
#include <iostream>\n\nclass Base {\npublic:\n    ~Base() { \n        std::cout << "Base destructor\\n"; \n    }\n};\n\nclass Derived : public Base {\nprivate:\n    int* big_data = new int[1000]; // recursos que deben liberarse\npublic:\n    Derived() { std::cout << "Derived created\\n"; }\n    ~Derived() {\n        delete[] big_data;\n        std::cout << "Derived destructor\\n";\n    }\n};\n\nint main() {\n    Base* ptr = new Derived();\n    delete ptr;  // ¡Ups! Solo se llamará a ~Base()\n    return 0;\n}
\n\n

Salida:

\n
Derived created\nBase destructor
\n\n

El array big_data se ha fugado. El destructor de Derived no se llamó porque el compilador, al ver el tipo Base*, llamó al destructor de esa clase. Y no es virtual.

\n\n

Solución: añadir virtual antes del destructor de Base:

\n\n
class Base {\npublic:\n    virtual ~Base() = default;  // o cuerpo vacío\n};
\n\n

Ahora, al hacer delete ptr, primero se ejecutará ~Derived() y luego ~Base(). Los recursos se liberan, no hay fugas de memoria y el analizador estático está contento.

\n\n

Cuándo es obligatorio el destructor virtual (regla de hierro)

\n\n

Solo hay un caso incondicional: si la clase está diseñada para uso polimórfico mediante un puntero o referencia a la clase base, y los objetos de las clases derivadas se eliminarán a través de ese puntero.

\n\n

En la práctica, esto significa:

\n
    \n
  • La clase tiene al menos una función virtual (pura o no)
  • \n
  • Planeas usar Base* o Base& para trabajar con Derived
  • \n
  • Los objetos se crean en el heap (new) y se eliminan con delete
  • \n
\n\n

Ejemplo: la jerarquía clásica de figuras:

\n\n
class Shape {\npublic:\n    virtual double area() const = 0;\n    virtual ~Shape() = default;  // obligatorio para una clase abstracta\n};\n\nclass Circle : public Shape {\n    double radius;\npublic:\n    explicit Circle(double r) : radius(r) {}\n    double area() const override { return 3.14159 * radius * radius; }\n};\n\nint main() {\n    std::vector<std::unique_ptr<Shape>> shapes;\n    shapes.push_back(std::make_unique<Circle>(5.0));\n    // unique_ptr se encargará del delete, pero el destructor de Shape debe ser virtual\n    return 0;\n}
\n\n

Observa: incluso si usas std::unique_ptr (que es el enfoque recomendado en 2025), el destructor virtual sigue siendo necesario. unique_ptr llama a delete a través del puntero a la clase base —

Blogs

Book Recommendations