Un destructor es una función miembro especial que se llama cuando finaliza la vida útil de un objeto. El propósito del destructor es liberar los recursos que el objeto puede haber adquirido durante su vida.

Sintaxis

~nombre de la clase(); (1)
virtual~nombre de la clase(); (2)
decl-specifier-seq(Opcional)~nombre de la clase() = default; (3) (desde C ++ 11)
decl-specifier-seq(Opcional)~nombre de la clase() = delete; (4) (desde C ++ 11)
attr(Opcional)decl-specifier-seq(Opcional)expresión-id(void(Opcional))excepto(Opcional)attr(Opcional); (5)

1) Declaración típica de un destructor2) El destructor virtual generalmente se requiere en una clase base3) Obligando a un destructor a ser generado por el compilador4) Deshabilitar el destructor implícito5) Sintaxis formal de una declaración de destructor

decl-specifier-seq friend, inline, virtual, o nada (sin tipo de devolución)
expresión-id dentro de una definición de clase, el símbolo ~ Seguido por el nombre de la clase. Dentro de una plantilla de clase, el símbolo ~ seguido del nombre de la instanciación actual de la plantilla. En el ámbito del espacio de nombres o en una declaración de amigo dentro de una clase diferente, especificador-nombre-anidado seguido del símbolo ~ Seguido por el nombre de la clase que es la misma clase que la nombrada por el especificador-nombre-anidado. En cualquier caso, el nombre debe ser el nombre real de la clase o plantilla, y no un typedef. Toda la expresión id puede estar rodeada de paréntesis que no cambian su significado.
attr(C ++ 11) secuencia opcional de cualquier número de atributos
excepto especificación de excepción como en cualquier declaración de función (ya sea especificación de excepción dinámica(obsoleto)(eliminado en C ++ 17) o no, excepto la especificación(C ++ 11))

Excepto que si no se proporciona explícitamente una especificación de excepción, se considera que la especificación de excepción es una que sería utilizada por el destructor declarado implícitamente (ver más abajo). En la mayoría de los casos, esto es noexcept(true). Por lo tanto, un destructor lanzador debe declararse explícitamente noexcept(false).

(desde C ++ 11)

Explicación

Se llama al destructor siempre que finaliza la vida útil de un objeto, lo que incluye.

  • terminación de programa, para objetos con static duración de almacenamiento
  • salida de subproceso, para objetos con duración de almacenamiento local de subproceso
(desde C ++ 11)
  • fin de alcance, para objetos con duración de almacenamiento automático y para provisionales cuya vida se extendió mediante la vinculación a una referencia
  • eliminar-expresión, para objetos con duración de almacenamiento dinámico
  • fin de la expresión completa, para temporales sin nombre
  • desenrollado de pila, para objetos con duración de almacenamiento automático cuando una excepción escapa de su bloque, sin ser detectada.

El destructor también se puede llamar directamente, por ejemplo, para destruir un objeto que se construyó usando la ubicación nueva o mediante una función de miembro de asignación como std::allocator::destroy(), para destruir un objeto que se construyó a través del asignador. Tenga en cuenta que llamar a un destructor directamente para un objeto ordinario, como una variable local, invoca un comportamiento indefinido cuando se vuelve a llamar al destructor, al final del alcance.

En contextos genéricos, la sintaxis de la llamada al destructor se puede utilizar con un objeto de tipo que no sea de clase; esto se conoce como llamada de pseudodestructor: ver operador de acceso a miembros.

Destructor declarado implícitamente

Si no se proporciona un destructor declarado por el usuario para un tipo de clase (struct, class, o union), el compilador siempre declarará un destructor como un inline public miembro de su clase.

Al igual que con cualquier función miembro especial declarada implícitamente, la especificación de excepción del destructor declarado implícitamente no se lanza a menos que el destructor de cualquier base o miembro potencialmente construido está potencialmente lanzando(desde C ++ 17)la definición implícita invocaría directamente una función con una especificación de excepción diferente(hasta C ++ 17). En la práctica, los destructores implícitos son noexcept a menos que la clase sea “envenenada” por una base o miembro cuyo destructor sea noexcept(false).

Destructor declarado implícitamente eliminado

El destructor implícitamente declarado o predeterminado para la clase T es indefinido (hasta C ++ 11)definido como eliminado(desde C ++ 11) si alguno de los siguientes es true:

  • T tiene un nostatic miembro de datos que no se puede destruir (tiene un destructor eliminado o inaccesible)
  • T tiene una clase base directa o virtual que no se puede destruir (tiene destructores eliminados o inaccesibles)
  • T es una unión y tiene un miembro variante con un destructor no trivial.
(desde C ++ 11)
  • El destructor declarado implícitamente es virtual (porque la clase base tiene un destructor virtual) y la búsqueda de la función de desasignación (operator delete() da como resultado una llamada a una función ambigua, eliminada o inaccesible.

Destructor trivial

El destructor de la clase T es trivial si todo lo siguiente es true:

  • El destructor no lo proporciona el usuario (es decir, se declara implícitamente o se define explícitamente como predeterminado en su primera declaración)
  • El destructor no es virtual (es decir, el destructor de la clase base no es virtual)
  • Todas las clases base directas tienen destructores triviales
  • Todos los nostatic miembros de datos de tipo de clase (o array de tipo de clase) tienen destructores triviales

Un destructor trivial es un destructor que no realiza ninguna acción. Los objetos con destructores triviales no requieren una expresión de eliminación y pueden eliminarse simplemente desasignando su almacenamiento. Todos los tipos de datos compatibles con el lenguaje C (tipos POD) son trivialmente destructibles.

Destructor definido implícitamente

Si un destructor declarado implícitamente no se elimina, el compilador lo define implícitamente (es decir, el cuerpo de una función se genera y compila) cuando se usa odr. Este destructor definido implícitamente tiene un cuerpo vacío.

Secuencia de destrucción

Tanto para los destructores definidos por el usuario como para los definidos implícitamente, después de que se ejecuta el cuerpo del destructor, el compilador llama a los destructores para todos losstatic miembros no variantes de la clase, en orden inverso de declaración, luego llama a los destructores de todas las clases base directas no virtuales en orden inverso de construcción (que a su vez llaman a los destructores de sus miembros y sus clases base, etc.), y luego, si este objeto es de la clase más derivada, llama a los destructores de todas las bases virtuales.

Incluso cuando se llama directamente al destructor (p. Ej. obj.~Foo();), la declaración de devolución en ~Foo() no devuelve el control al llamador inmediatamente: llama primero a todos los destructores de miembros y bases.

Destructores virtuales

Eliminar un objeto a través del puntero a la base invoca un comportamiento indefinido a menos que el destructor en la clase base sea virtual:

classBasepublic:virtual~Base();classDerived:publicBase;
Base* b =new Derived;delete b;// safe

Una pauta común es que un destructor para una clase base debe ser ya sea público y virtual o protegido y no virtual.

Destructores virtuales puros

Un destructor puede declararse puramente virtual, por ejemplo, en una clase base que necesita ser abstracta, pero no tiene otras funciones adecuadas que puedan declararse puramente virtual. Dicho destructor debe tener una definición, ya que todos los destructores de clase base siempre se llaman cuando se destruye la clase derivada:

classAbstractBasepublic:virtual~AbstractBase()=0;;AbstractBase::~AbstractBase()classDerived:publicAbstractBase;// AbstractBase obj;   // compiler error
Derived obj;// OK

Excepciones

Como cualquier otra función, un destructor puede terminar lanzando una excepción(esto generalmente requiere que se declare explícitamente noexcept(false))(desde C ++ 11), sin embargo, si se llama a este destructor durante el desenrollado de la pila, std::terminate se llama en su lugar.

A pesar de que std::uncaught_exception a veces se puede usar para detectar el desenrollado de la pila en progreso, generalmente se considera una mala práctica permitir que cualquier destructor termine lanzando una excepción. No obstante, algunas bibliotecas utilizan esta función, como SOCI y Galera 3, que se basan en la capacidad de los destructores de temporales sin nombre para lanzar excepciones al final de la expresión completa que construye el temporal.

Ejemplo

#includestructAint i;A(int i ):i( i )
        std::cout <<"ctor a"<< i <<'n';~A()
        std::cout <<"dtor a"<< i <<'n';;
 
A a0(0);intmain()
    A a1(1);
    A* p;// nested scope
        A a2(2);
        p =newA(3);// a2 out of scopedelete p;// calls the destructor of a3

Producción:

ctor a0
ctor a1
ctor a2
ctor a3
dtor a2
dtor a3
dtor a1
dtor a0