Destructores
Un destructor es una función miembro especial que se llama cuando la duración de un objeto termina. El propósito del destructor es liberar los recursos que el objeto pudo haber adquirido durante su duración.
Un destructor no debe ser una corrutina. (desde C++20)
[editar]Sintaxis
~ nombre_de_clase(); | (1) | ||||||||
virtual ~ nombre_de_clase(); | (2) | ||||||||
sec-decl-especificadores(opcional)~ nombre_de_clase() = default; | (3) | (desde C++11) | |||||||
sec-decl-especificadores(opcional)~ nombre_de_clase() = delete; | (4) | (desde C++11) | |||||||
atrib(opcional)sec-decl-especificadores(opcional)expresión-id( void(opcional)) except(opcional)atrib(opcional)cláusula-requiere(opcional); | (5) | ||||||||
sec-decl-especificadores | - | friend , inline , virtual ,constexpr , consteval (desde C++20) | ||
expresión-id | - | Dentro de una definición de clase, el símbolo ~ seguido de nombre_de_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 friend dentro de una clase distinta, especificador de nombre anidado seguido del símbolo ~ seguido de nombre_de_clase que es la misma clase que la nombrada por el especificador de nombre anidado. De cualquier forma, el nombre debe ser el nombre actual de la clase o plantilla y no una definición de tipo (typedef ). La expresión-id completa puede estar rodeada por paréntesis, lo que no cambia su significado. | ||
atrib(C++11) | - | Secuencia opcional de cualquier número de atributos | ||
except | - | Especificación de excepción como en cualquier declaración de función (ya sea la especificación de excepción dinámica(en desuso)(eliminado en C++17) o la especificación noexcept (C++11)).
| ||
cláusula-requiere | - | (desde C++20) cláusula-requiere que declara las restricciones asociadas para el destructor potencial, que deben cumplirse para que el destructor potencial sea seleccionado como destructor . |
[editar]Explicación
Se llama al destructor siempre que termine la duración de un objeto, lo que incluye
- la terminación del programa, para los objetos con duración de almacenamiento estática;
| (desde C++11) |
- el fin del ámbito, para objetos con duración de almacenamiento automática y para los temporales cuya vida se extendió al vincularlos con una referencia;
- la expresión
delete
, para objetos con duración de almacenamiento dinámica; - el final de la expresión completa, para temporales sin nombre;
- el desenredo de la pila, para objetos con duración de almacenamiento automática cuando una excepción se escapa de su bloque sin ser capturada.
El destructor también puede ser llamado directamente. Por ejemplo, para destruir un objeto que se construyó utilizando new
de ubicación o a través una función miembro de asignación de memoria tal como std::allocator::destroy(), para destruir un objeto que se construyó a través del asignador de memoria. Observa que llamar a un destructor directamente para un objeto ordinario, tal como una variable local, invoca un comportamiento indefinido cuando el destructor se llama de nuevo, 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 un llamada a pseudodestructor: véase operador de acceso a miembro.
Destructor potencialUna clase puede tener uno o más destructores potenciales, uno de los cuales se selecciona como el destructor de la clase. Para determinar que destructor potencial es el destructor, al final de la definición de la clase se realiza la resolución de sobrecarga entre los destructores potenciales declarados en la clase con una lista de argumentos vacía. Si falla la resolución de sobrecarga, el programa está mal formado. La selección del destructor no hace uso odr del destructor seleccionado, y el destructor seleccionado puede ser eliminado. Todos los destructores potenciales son funciones miembro especiales. Si no se proporciona un destructor potencial declarado por el usuario para la clase | (desde C++20) |
[editar]Destructor implícitamente declarado
Si no se proporciona un destructor potencial(desde C++20) declarado por el usuario para un tipo clase (struct, class, o union), el compilador siempre declarará un destructor como un miembro inline public
de su clase.
Al igual que cualquier función especial implícitamente declarada, la especificación de excepción del destructor implícitamente declarado no lanza, a menos que el destructor de cualquier base o miembro potencialmente construidos potencialmente lancen(desde C++17)la definición implícita invocaría directamente a una función con una especificación de excepción distinta(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 sea noexcept(false)
.
[editar]Destructor implícitamente declarado eliminado
El destructor implícitamente declarado o explícito por defecto para la clase T
es indefinido (hasta C++11)definido como eliminado(desde C++11) si cualquiera de lo siguiente es verdadero:
T
tiene un dato miembro no estático que no puede destruirse (tiene un destructor eliminado o inaccesible);T
tiene una clase base directa o virtual que no puede destruirse (tiene destructores eliminados o inaccesibles);
| (desde C++11) |
- el destructor implícitamente declarado es virtual (porque la clase base tiene un destructor virtual) y la búsqueda de la función de desasignación de memoria (operator delete() resulta en una llamada a una función ambigua, eliminada o inaccesible.
Un destructor potencial predeterminado explícito para | (desde C++20) |
[editar]Destructor trivial
El destructor para una clase T
es trivial si todo lo siguiente es verdadero:
- El destructor no es proporcionado por el usuario (que quiere decir que es ya sea implícitamente declarado, o explícitamente definido como por defecto (=default) 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 datos miembro no estáticos de tipo clase (o array de tipo 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 delete y puede disponerse de ellos simplemente desasignando su almacenamiento. Todos los tipos de datos compatibles con el lenguaje C (tipos POD, o plain-old data types) son trivialmente destruibles.
[editar]Destructor implícitamente definido
Si un destructor implícitamente declarado no está eliminado, está implícitamente definido (es decir, un cuerpo de función es generado y compilado) por el compilador cuando hay uso ODR. Este destructor implícitamente definido tiene un cuerpo vacío. Si esto satisface los requerimientos de un destructor constexpr, el destructor generado es constexpr
.(desde C++20)
[editar]Secuencia de destrucción
Tanto para los destructores definidos por el usuario como para los destructores implícitamente definidos, después de ejecutar el cuerpo del destructor y destruir los objetos automáticos alojados en el cuerpo, el compilador llama a los destructores para todos los miembros de datos no estáticos, no variantes, de la clase, en el orden inverso de su declaración. Luego llama a los destructores de todas las clases base no virtuales directas en orden inverso al de la construcción (que a su vez llama 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 al destructor directamente (p. ej., obj.~Foo();), la instrucción return en ~Foo() no devuelve el control al llamador de inmediato: primero llama a todos esos destructores miembros y base.
[editar]Destructores virtuales
La eliminación de un objeto a través de un puntero a la base invoca un comportamiento indefinido, a menos que el destructor de la clase base sea virtual:
class Base {public:virtual ~Base(){}}; class Derivada :public Base {}; Base* b = new Derivada; delete b;// seguro
Una guía común es que el destructor de la clase base debe ser ya sea público y virtual o protegido y no virtual
[editar]Destructores virtuales puros
Un destructor potencial(desde C++20) 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 las clases base siempre se llaman cuando la clase derivada se destruye:
class BaseAbstracta {public:virtual ~ BaseAbstracta()=0;}; BaseAbstracta::~ BaseAbstracta(){} class Derivada :public BaseAbstracta {}; // BaseAbstracta obj; // ERROR de compilación Derivada obj;// de acuerdo
[editar]Excepciones
Como cualquier otra función, un destructor puede terminar lanzando una excepción(esto habitualmente requiere que se declare explícitamente noexcept(false))(desde C++11), sin embargo, si sucede que se llama a este destructor durante el desenredo de pila, en su lugar se llama a std::terminate.
Aunque a veces puede usarse a std::uncaught_exception para detectar un desenredo de pila en curso, en general se considera una mala práctica permitir que cualquier destructor termine lanzando una excepción. Sin embargo, esta funcionalidad se utiliza por algunas bibliotecas, como SOCI y Galera 3, que dependen de la habilidad de los destructores de temporales sin nombre de lanzar excepciones al final de la expresión completa que construye el temporal.
std::experimental::scope_success en la Biblioteca de fundamentos TS v3 puede tener un destructor que potencialmente lance, que lanza una excepción cuando se regresa del ámbito normalmente y la función de salida lanza una excepción.
[editar]Ejemplo
Salida:
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
[editar]Informe de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 193 | C++98 | no se especificaba si los objetos automáticos en un destructor se destruyen 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 de destructor era defectuosa (tiene el mismo problema que CWG issue 194 y CWG issue 263) | cambió la sintaxis a una sintaxis de declarador de función especializada |
CWG 1241 | C++98 | los miembros estáticos pueden destruirse justo después de la ejecución del destructor | solo destruye miembros no estáticos |
CWG 2180 | C++98 | un destructor para la clase X llama a los destructores para lasclases base directas virtuales de X | esos destructores no se llaman |