Destructor virtual puro de C++

Abdul Mateen 12 octubre 2023
  1. Llame a la función de clase derivada utilizando el puntero de clase base
  2. Función virtual pura y clase abstracta
  3. Destructor virtual puro y virtual
  4. Destructor virtual puro
Destructor virtual puro de C++

En este tutorial, discutiremos los destructores virtuales puros en C++. Sin embargo, para comprender completamente el concepto, debemos comprender diferentes conceptos relacionados.

Primero, veremos el método para llamar a una función de clase derivada utilizando un puntero de clase base, y luego discutiremos los problemas de la llamada de destructor en la herencia. Finalmente, discutiremos el destructor virtual puro y sus problemas de implementación.

Llame a la función de clase derivada utilizando el puntero de clase base

Un puntero de clase base tiene el poder de apuntar a cualquiera de sus objetos de clase derivados. Esto nos evita declarar y administrar múltiples punteros de clases derivadas para manejar diferentes objetos de clases derivadas.

Sin embargo, un puntero de clase base no puede llamar a los métodos de clase derivados en general. Por ejemplo, vea el siguiente código:

#include <iostream>
using namespace std;
class P {};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

En la compilación, este código muestra el siguiente error:

virtual_destructor0.cpp: In function ‘int main()’:
virtual_destructor0.cpp:12:7: error: ‘class P’ has no member named ‘f’
  ptr->f();

Si declaramos la misma función en la clase base, el compilador llama a la función de la clase base en lugar de a la función de la clase derivada. Vea el siguiente código, donde hemos agregado la función f en la clase P:

#include <iostream>
using namespace std;
class P {
 public:
  void f() { cout << "Function P\n"; }
};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

Después de agregar la función f, vea la salida:

Function P

Esto significa que el puntero de la clase base llama a la función de la clase base, no a la función de la clase derivada. Deducimos la regla de que un puntero de clase puede llamar a una función desde su clase, no desde la clase del objeto que la contiene.

Sin embargo, hay otro concepto crucial llamado Función Virtual. La función virtual permite que las clases derivadas anulen la función de la clase base, a la que puede llamar el puntero de la clase base.

La anulación de función significa que la clase derivada proporciona su propia funcionalidad para la función de clase base correspondiente. La clase base escribe la palabra clave virtual con sus funciones para permitir la anulación en la clase derivada.

Nuevamente, vea el código:

#include <iostream>
using namespace std;
class P {
 public:
  virtual void f() { cout << "Function P\n"; }
};
class C : public P {
 public:
  void f() { cout << "Function C\n"; }
};
int main() {
  P *ptr = new C;
  ptr->f();
  return 0;
}

Tenga en cuenta la adición de la palabra clave virtual antes de la función de clase base f(). Después de la nueva adición, vea el resultado:

Function C

Esto significa que el puntero de la clase base ahora está llamando a una función de acuerdo con el tipo de objeto al que hace referencia este puntero base (es decir, se ejecutará el miembro de la clase derivada). Recuerde, el comportamiento anterior era llamar a miembros de tipo puntero (es decir, clase base).

Sin embargo, es elección del implementador de la clase derivada otorgar la funcionalidad de la clase virtual, lo que significa que la clase derivada puede anular o no la función base virtual.

A veces, el arquitecto (diseñador) de clases impone una clase derivada para implementar funciones de clase base, lo que involucra más conceptos como clase base abstracta (también llamada ABC) y función virtual pura.

Los discutiremos rápidamente. Puede visitar los sitios web referidos si es necesario.

Función virtual pura y clase abstracta

Las clases base pueden declarar una función virtual pura para imponer la implementación de métodos base virtuales en clases derivadas. Una función virtual pura no tiene implementación.

Además, una clase que tiene una o más funciones virtuales puras se convierte en una clase abstracta. Las clases abstractas no son instanciables (sin embargo, podemos declarar sus punteros, apuntando a objetos de clase derivados).

La sintaxis de una función virtual pura es ligeramente diferente:

virtual void f() = 0;

La función virtual pura no tiene definición ni cuerpo; por lo tanto, no es exigible. Sin embargo, impone clases derivadas para implementar funciones virtuales puras.

De lo contrario, la clase derivada se convierte en abstracta (es decir, no instanciable).

Por el contrario, para hacer que una clase sea abstracta, solo necesitamos hacer una función abstracta. Nos referiremos a esta oración más adelante en el artículo.

Destructor virtual puro y virtual

En primer lugar, tenga en cuenta que el destructor no se puede anular. Sin embargo, para llamar tanto a los destructores de clases base como a los secundarios, el destructor en la clase (base) del puntero debe ser virtual.

De lo contrario, solo se llamará al destructor de clase hijo. Por ejemplo, veamos el siguiente código:

#include <iostream>
using namespace std;
class P {
 public:
  ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
 public:
  ~C() { cout << "C Class Destructor\n"; }
};
int main() {
  P *ptr = new C;
  delete ptr;
  return 0;
}

Producción :

P Class Destructor

Aquí puede ver que el puntero es de clase P, y el objeto es de clase C. La operación eliminar solo llama al destructor de la clase base, lo que significa que si tenemos que eliminar recursos de la clase derivada, no podemos hacerlo.

Para ello, debemos declarar un destructor virtual en la clase base. Como ya sabéis, para ello tenemos que añadir una palabra clave virtual con el destructor de clases base.

Aquí está el código de ejemplo:

#include <iostream>
using namespace std;
class P {
 public:
  virtual ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
 public:
  ~C() { cout << "C Class Destructor\n"; }
};
int main() {
  P *ptr = new C;
  delete ptr;
  return 0;
}

Producción :

C Class Destructor
P Class Destructor

Destructor virtual puro

Finalmente, volviendo al tema principal, resolvamos el problema de llamar a los destructores de las clases principal y derivada a través de un destructor virtual puro.

Como se mencionó anteriormente, un destructor difiere de otras funciones y no existe el concepto de anular el destructor. Por lo tanto, un destructor virtual puro no puede obligar a una clase derivada a implementar un destructor.

Ahora, veamos las cosas desde otro ángulo. Para hacer una clase abstracta, debemos declarar al menos una función virtual pura.

En tal caso, las clases derivadas estarán obligadas a implementar la función virtual pura para convertirse en una clase concreta. Por lo tanto, hacemos que una clase sea abstracta al declarar un destructor virtual puro en esta clase, lo que no obligará a la clase derivada a implementar nada.

Probabilidades de un destructor virtual puro

Recuerda siempre el teorema de No hay almuerzo gratis; declarar un destructor virtual puro tiene algunas complicaciones.

Veámoslos a través del siguiente ejemplo:

class P {
 public:
  virtual ~P() = 0;
};

Si compilamos este código, se presenta el siguiente error:

/tmp/ccZfsvAh.o: In function `C::~C()':
virtual_destructor4.cpp:(.text._ZN1CD2Ev[_ZN1CD5Ev]+0x22): undefined reference to `P::~P()'
collect2: error: ld returned 1 exit status

Afortunadamente, la mayoría de los problemas complejos del mundo tienen soluciones simples. Este error ocurre porque el compilador está buscando la definición del destructor, que no está disponible.

Por lo tanto, la solución es definir el destructor virtual puro. Parece una contradicción; compilador que exige una definición de una función virtual pura?

Sí, esta es una de las otras posibilidades de C++. No podemos implementar un destructor virtual puro dentro de la clase; sin embargo, podemos implementarlo fuera de la clase.

Mira el siguiente código:

class P {
 public:
  virtual ~P() = 0;
};
P::~P() { cout << "P Class Destructor\n"; }

En la última línea, hemos definido un destructor virtual puro fuera de la clase y no habrá ningún error de compilación. Además, podremos llamar tanto a la clase base como al destructor de clases derivadas:

C Class Destructor
P Class Destructor

Para concluir, para hacer una clase base abstracta, podemos declarar un destructor virtual puro dentro de ella; sin embargo, debemos definir este destructor virtual puro fuera de la clase (donde podemos limpiar cualquier recurso/memoria). De esta manera, podemos llamar tanto a la clase base como al destructor de clase derivada.

Artículo relacionado - C++ Destructor