HOWTO · C++

Llamar a un destructor explícitamente en C++

Este artículo demuestra cómo llamar a un destructor explícitamente en C++

En esta página

En C++, escribe obj.~ClassName() para invocar explícitamente el destructor de un objeto. Esto termina la duración de vida de ese objeto, así que no vuelvas a usarlo a menos que se cree un objeto nuevo en su almacenamiento. Las llamadas manuales son poco habituales; la limpieza normal al salir del ámbito o un tipo propietario como std::unique_ptr suelen ser opciones más seguras.

Usa la notación obj.~ClassName() para llamar explícitamente a una función destructor

Un destructor libera los recursos que posee un objeto cuando termina su duración de vida. El compilador lo llama automáticamente cuando un objeto automático sale de su ámbito. Para un puntero devuelto por new, delete llama al destructor y luego libera el almacenamiento del objeto. No combines estas vías normales con una llamada manual adicional: destruir explícitamente un objeto automático y dejar después que salga de su ámbito intenta destruir un objeto cuya duración de vida ya terminó. Ese comportamiento es indefinido; no garantiza que se impriman dos mensajes. Del mismo modo, destruir manualmente un objeto creado con new y aplicar después delete al mismo puntero no es una secuencia válida de limpieza.

Una llamada explícita resulta útil en código de bajo nivel cuidadosamente gestionado, por ejemplo, cuando un objeto ocupa almacenamiento proporcionado por quien llama y el programa va a construir allí otro objeto. El siguiente ejemplo usa placement new para iniciar la duración de vida de un objeto en un búfer de bytes, invoca su destructor y luego construye un reemplazo en la misma dirección. La última llamada explícita termina la duración de vida del reemplazo; el búfer de bytes no destruye automáticamente los objetos almacenados en él.

La llamada usa la expresión del objeto, seguida de .~ y el nombre de la clase: para un objeto llamado obj de la clase Widget, la forma es obj.~Widget(). El destructor no devuelve ningún valor ni acepta argumentos. También se aplican las reglas de acceso, por lo que el código que no tenga permiso de acceso no puede llamar a un destructor privado. La llamada ejecuta la limpieza del destructor, pero no libera el almacenamiento obtenido de un asignador o de new; la gestión del almacenamiento es una responsabilidad aparte.

En el ejemplo, alignas(Widget) da al búfer de bytes la alineación necesaria para un Widget, y su tamaño basta para una instancia. Placement new construye el objeto en ese búfer existente en lugar de solicitar una asignación aparte. La primera llamada explícita termina la duración de vida de la primera instancia antes de construir el reemplazo. Después, el reemplazo también se destruye explícitamente. Como el almacenamiento es una matriz local de bytes, al terminar su ámbito solo termina la duración de vida de la matriz; no se llama al destructor de un Widget colocado allí. Este patrón exige llevar la cuenta de qué objeto está vivo en el almacenamiento y garantizar que cada objeto construido se destruya exactamente una vez.

En el código habitual de una aplicación, una variable local es más sencilla: su destructor se ejecuta cuando el control sale del bloque, incluso al regresar de una función. std::unique_ptr<Widget> es una buena opción cuando se necesita propiedad dinámica, porque vincula la destrucción del objeto a la duración de vida del propietario. Si un objeto forma parte de un contenedor u otra clase, deja que ese propietario gestione su destrucción en vez de llamar manualmente al destructor del objeto miembro. Estas opciones hacen visible la limpieza en la estructura del programa y evitan usar un objeto después de que termine su duración de vida.

Ejecuta este ejemplo. El bloque de código y su salida se conservan exactamente como en el original; por ello, cualquier etiqueta visible dentro de ellos permanece en inglés.

#include <iostream>
#include <new>
#include <string>
#include <utility>

class Widget {
 public:
  explicit Widget(std::string name) : name_(std::move(name)) {
    std::cout << "created " << name_ << '\n';
  }

  ~Widget() { std::cout << "destroyed " << name_ << '\n'; }

 private:
  std::string name_;
};

int main() {
  alignas(Widget) unsigned char storage[sizeof(Widget)];
  Widget* obj = ::new (static_cast<void*>(storage)) Widget("first");
  obj->~Widget();
  obj = ::new (static_cast<void*>(storage)) Widget("second");
  obj->~Widget();
}
created first
destroyed first
created second
destroyed second

En el código que no gestiona directamente el almacenamiento y la duración de vida, deja que el ámbito de un objeto local termine de forma natural. Para la propiedad de objetos asignados dinámicamente, prefiere std::unique_ptr: su destructor usa delete cuando el propietario sale de su ámbito. No accedas a un puntero después de que se haya destruido el objeto al que apunta, ni invoques manualmente un destructor solo para liberar un recurso miembro; usa en su lugar un propietario o envoltorio de recursos adecuado de la biblioteca estándar.