Destructors
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) |
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 | - |
|
||||||
| 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:
- En una declaración de miembro que pertenece a la especificación de miembro de una clase o plantilla de clase, pero no es una declaración friend :
-
- 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
- terminación del programa , para objetos con duración de almacenamiento estática
|
(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 prospectivoUna 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
Ejecutar este código
#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:
- Se invoca explícita o implícitamente.
-
Una
new
expression
crea un array de objetos de tipo
T. -
El objeto resultante de una
return
statement
es de tipo
T. -
Un array está bajo
aggregate initialization
, y su tipo de elemento es
T. -
Un objeto de clase está bajo aggregate initialization, y tiene un miembro de tipo
TdondeTno es un tipo de anonymous union . -
Un
potentially constructed subobject
es de tipo
Ten un constructor no- delegating (since C++11) . -
Se construye un
exception object
de tipo
T.
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
|
(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.
|
(until C++26) |
|
(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 |