Namespaces
Variants

Destructors

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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 pudo haber adquirido durante su vida útil.

Un destructor no puede ser una coroutine .

(since C++20)

Contenidos

Sintaxis

Destructores (hasta C++20) Destructores prospectivos (desde C++20) se declaran utilizando declaradores de función miembro de la siguiente forma:

class-name-with-tilde ( parameter-list  (opcional) ) except  (opcional) attr  (opcional)
class-name-with-tilde - una expresión de identificador , posiblemente seguida de una lista de atributos , y (desde C++11) posiblemente encerrada entre un par de paréntesis
parameter-list - lista de parámetros (debe estar vacía o ser void )
except -

especificación de excepciones dinámicas

(hasta C++11)

ya sea especificación de excepciones dinámicas
o especificación noexcept

(desde C++11)
(hasta C++17)

especificación noexcept

(desde C++17)
attr - (desde C++11) una lista de atributos

Los únicos especificadores permitidos en los especificadores de declaración de una destructor (desde C++20) prospectivo son constexpr , (desde C++11) friend , inline y virtual (en particular, no se permite ningún tipo de retorno).

La expresión identificadora de class-name-with-tilde debe tener una de las siguientes formas:

  • Para clases, la expresión identificadora es ~ seguida del injected-class-name de la clase que la contiene inmediatamente.
  • Para plantillas de clase, la expresión identificadora es ~ seguida de un nombre de clase que denomina la current instantiation (until C++20) el injected-class-name (since C++20) de la plantilla de clase que la contiene inmediatamente.
  • De lo contrario, la expresión identificadora es un identificador calificado cuyo identificador no calificado terminal es ~ seguido por el nombre de clase inyectado de la clase nominada por las partes no terminales del identificador calificado.

Explicación

El destructor se invoca implícitamente cada vez que la duración de vida de un objeto termina, lo cual incluye

  • finalización de hilo, para objetos con duración de almacenamiento local al hilo
(since C++11)
  • fin del ámbito, para objetos con duración de almacenamiento automático y para temporales cuya vida fue extendida al vincularse a una referencia
  • delete expression , para objetos con duración de almacenamiento dinámico
  • fin de la expresión completa, para temporales anónimos
  • stack unwinding , para objetos con duración de almacenamiento automático cuando una excepción escapa de su bloque, no capturada.

El destructor también puede invocarse explícitamente.

Destructor prospectivo

Una clase puede tener uno o más destructores prospectivos, uno de los cuales es seleccionado como el destructor de la clase.

Para determinar qué destructor prospectivo es el destructor, al final de la definición de la clase, resolución de sobrecarga se realiza entre los destructores prospectivos declarados en la clase con una lista de argumentos vacía. Si la resolución de sobrecarga falla, el programa está mal formado. La selección del destructor no odr-utiliza el destructor seleccionado, y el destructor seleccionado puede estar eliminado.

Todos los destructores prospectivos son funciones miembro especiales. Si no se proporciona ningún destructor prospectivo declarado por el usuario para la clase T , el compilador siempre declarará implícitamente uno, y el destructor prospectivo declarado implícitamente también es el destructor para T .

#include <cstdio>
#include <type_traits>
template<typename T>
struct A
{
    ~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); }
    ~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); }
    ~A() { std::puts("~A, T is anything else"); }
};
int main()
{
    A<int> a;
    A<int*> b;
    A<float> c;
}

Salida:

~A, T is anything else
~A, T is a pointer
~A, T is integral
(desde C++20)

Destructor potencialmente invocado

El destructor para la clase T es potencialmente invocado en las siguientes situaciones:

Si un destructor potencialmente invocado está eliminado o (since C++11) no es accesible desde el contexto de la invocación, el programa está mal formado.

Destructor declarado implícitamente

Si no se proporciona ningún destructor declarado por el usuario prospectivo (desde C++20) para un tipo de clase , el compilador siempre declarará un destructor como un miembro inline public de su clase.

Como con cualquier función miembro especial declarada implícitamente, la especificación de excepciones del destructor declarado implícitamente es no-lanzadora a menos que el destructor de cualquier base o miembro potencialmente construido sea potencialmente-lanzador (desde C++17) la definición implícita invocaría directamente una función con una especificación de excepciones diferente (hasta C++17) . En la práctica, los destructores implícitos son noexcept a menos que la clase esté "envenenada" por una base o miembro cuyo destructor es noexcept ( false ) .

Destructor definido implícitamente

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

Si esto satisface los requisitos de un destructor constexpr (hasta C++23) función constexpr (desde C++23) , el destructor generado es constexpr .

(desde C++20)


Destructor eliminado

El destructor declarado implícitamente o definido por defecto explícitamente para la clase T se define como eliminado si se cumple alguna de las siguientes condiciones:

  • está eliminado o es inaccesible desde el destructor de T , o
  • en el caso de que el subobjeto sea un miembro variante , es no trivial.
(hasta C++26)
  • T no es una unión, y tiene un subobjeto potencialmente construido no variante de tipo clase M (o posiblemente array multidimensional del mismo) tal que M tiene un destructor que está eliminado o es inaccesible desde el destructor de T .
  • T es una unión, y se cumple alguna de las siguientes condiciones:
  • La resolución de sobrecarga para seleccionar un constructor que inicialice por defecto un objeto de tipo T falla o selecciona un constructor que está eliminado o es no trivial.
  • T tiene un miembro variante V de tipo clase M (o posiblemente array multidimensional del mismo) donde V tiene un inicializador por defecto y M tiene un destructor que es no trivial.
(desde C++26)
  • una ambigüedad, o
  • una función que está eliminada o es inaccesible desde el destructor.

Un destructor prospectivo definido por defecto explícitamente para T se define como eliminado si no es el destructor de T .

(desde C++20)
(desde C++11)

Destructor trivial

El destructor para la clase T es trivial si se cumplen todas las siguientes condiciones:

  • El destructor es implicitamente-declarado (hasta C++11) no proporcionado por el usuario (desde C++11) .
  • El destructor no es virtual.
  • Todas las clases base directas tienen destructores triviales.
  • Cada miembro de datos no estático de tipo clase (o arreglo de tipo clase) tiene un destructor trivial.
(until C++26)
  • O bien T es una unión, o cada miembro de datos no estático no variante de tipo clase (o arreglo de tipo clase) tiene un destructor trivial.
(since C++26)

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

Secuencia de destrucción

Para destructores definidos por el usuario o definidos implícitamente, después de ejecutar el cuerpo del destructor y destruir cualquier objeto automático asignado dentro del cuerpo, el compilador llama a los destructores de todos los miembros de datos no estáticos 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 el destructor se llama directamente (por ejemplo, obj.~Foo ( ) ; ), la declaración return en ~Foo ( ) no devuelve el control al llamador inmediatamente: primero llama a todos esos destructores de miembros y bases.

Destructores virtuales

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

class Base
{
public:
    virtual ~Base() {}
};
class Derived : public Base {};
Base* b = new Derived;
delete b; // seguro

Una guía común es que un destructor para una clase base debe ser o público y virtual o protegido y no virtual .

Destructores puramente virtuales

Un destructor (since C++20) prospectivo puede declararse virtual puro , por ejemplo en una clase base que necesita hacerse abstracta, pero no tiene otras funciones adecuadas que puedan declararse virtuales puras. Un destructor virtual puro debe tener una definición, ya que todos los destructores de la clase base siempre se invocan cuando se destruye la clase derivada:

class AbstractBase
{
public:
    virtual ~AbstractBase() = 0;
};
AbstractBase::~AbstractBase() {}
class Derived : public AbstractBase {};
// AbstractBase obj; // error del compilador
Derived obj;         // OK

Excepciones

Como cualquier otra función, un destructor puede terminar lanzando una excepción (esto generalmente requiere que sea declarado explícitamente noexcept ( false ) ) (since C++11) , sin embargo si este destructor resulta ser llamado durante el desenrollado de pila , std::terminate es llamado en su lugar.

Aunque std::uncaught_exceptions puede utilizarse a veces para detectar el desenrollado de pila en progreso, generalmente se considera una mala práctica permitir que cualquier destructor termine lanzando una excepción. Sin embargo, esta funcionalidad es utilizada por algunas bibliotecas, como SOCI y Galera 3 , que dependen de la capacidad de los destructores de temporales anónimos para lanzar excepciones al final de la expresión completa que construye el temporal.

std::experimental::scope_success en Library fundamental TS v3 puede tener un destructor potencialmente lanzador , que lanza una excepción cuando el ámbito se sale normalmente y la función de salida lanza una excepción.

Notas

Llamar a un destructor directamente para un objeto ordinario, como una variable local, invoca un comportamiento indefinido cuando el destructor se llama nuevamente, al final del ámbito.

En contextos genéricos, la sintaxis de llamada al destructor puede utilizarse con un objeto de tipo no-clase; esto se conoce como llamada pseudo-destructora: consulte operador de acceso a miembro .

Macro de prueba de características Valor Std Característica
__cpp_trivial_union 202502L (C++26) Relajación de los requisitos de trivialidad para las funciones miembro especiales de uniones

Ejemplo

#include <iostream>
struct A
{
    int i;
    A(int num) : i(num)
    {
        std::cout << "ctor a" << i << '\n';
    }
    (~A)() // but usually ~A()
    {
        std::cout << "dtor a" << i << '\n';
    }
};
A a0(0);
int main()
{
    A a1(1);
    A* p;
    { // nested scope
        A a2(2);
        p = new A(3);
    } // a2 out of scope
    delete p; // calls the destructor of a3
}

Salida:

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

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 193 C++98 no estaba especificado si los objetos automáticos en un destructor
se destruían antes o después de la destrucción de los
subobjetos base y miembros de la clase
se destruyen
antes de destruir
esos subobjetos
CWG 344 C++98 la sintaxis del declarador del destructor era defectuosa (tenía el
mismo problema que CWG issue 194 y CWG issue 263
se cambió la sintaxis a una sintaxis de
declarador de función especializada
CWG 1241 C++98 los miembros estáticos podrían destruirse
justo después de la ejecución del destructor
solo destruir miembros
no estáticos
CWG 1353 C++98 las condiciones donde los destructores declarados implícitamente son
indefinidos no consideraban tipos de arreglos multidimensionales
considerar estos tipos
CWG 1435 C++98 el significado de "nombre de clase" en la
sintaxis del declarador del destructor no estaba claro
se cambió la sintaxis a una sintaxis de
declarador de función especializada
CWG 2180 C++98 el destructor de una clase que no es una clase más derivada
llamaría a los destructores de sus clases base virtuales directas
no llamará a esos destructores
CWG 2807 C++20 los especificadores de declaración podían contener consteval prohibido

Véase también