Hay varias formas, pero primero debe comprender por qué la limpieza de objetos es importante y, por lo tanto, la razón std::exit
está marginada entre los programadores de C ++.
RAII y Desbobinado de pila
C ++ utiliza un idioma llamado RAII , que en términos simples significa que los objetos deben realizar la inicialización en el constructor y la limpieza en el destructor. Por ejemplo, la std::ofstream
clase [puede] abrir el archivo durante el constructor, luego el usuario realiza operaciones de salida en él y, finalmente, al final de su ciclo de vida, generalmente determinado por su alcance, se llama al destructor que esencialmente cierra el archivo y descarga cualquier contenido escrito en el disco.
¿Qué sucede si no llega al destructor para vaciar y cerrar el archivo? ¡Quién sabe! Pero posiblemente no escribirá todos los datos que se suponía que debía escribir en el archivo.
Por ejemplo, considere este código
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
auto ptr = std::make_unique<int>();
inner_mad();
}
int main()
{
std::ofstream os("file.txt");
os << "Content!!!";
int possibility = /* either 1, 2, 3 or 4 */;
if(possibility == 1)
return 0;
else if(possibility == 2)
throw std::exception();
else if(possibility == 3)
mad();
else if(possibility == 4)
exit(0);
}
Lo que sucede en cada posibilidad es:
- Posibilidad 1: El retorno esencialmente deja el alcance de la función actual, por lo que sabe sobre el final del ciclo de vida de
os
llamar así a su destructor y realizar una limpieza adecuada al cerrar y vaciar el archivo al disco.
- Posibilidad 2: Lanzar una excepción también se ocupa del ciclo de vida de los objetos en el alcance actual, haciendo así una limpieza adecuada ...
- Posibilidad 3: ¡ Aquí el desbobinado de la pila entra en acción! Aunque se lanza la excepción
inner_mad
, el desbobinador pasará por la pila de mad
y main
para realizar la limpieza adecuada, todos los objetos se destruirán correctamente, incluidos ptr
y os
.
- Posibilidad 4: Bueno, ¿aquí?
exit
es una función de C y no es consciente ni compatible con los modismos de C ++. Que no se realice la limpieza de sus objetos, incluyendo os
en el mismo ámbito. Por lo tanto, su archivo no se cerrará correctamente y, por esta razón, ¡es posible que el contenido nunca se escriba en él!
- Otras posibilidades: Simplemente dejará el alcance principal, realizando una acción implícita
return 0
y, por lo tanto, tendrá el mismo efecto que la posibilidad 1, es decir, una limpieza adecuada.
Pero no esté tan seguro de lo que le acabo de decir (principalmente las posibilidades 2 y 3); continúe leyendo y descubriremos cómo realizar una limpieza adecuada basada en excepciones.
Posibles formas de terminar
Regreso de la principal!
Debe hacer esto siempre que sea posible; siempre prefiera regresar de su programa devolviendo un estado de salida adecuado de main.
La persona que llama de su programa, y posiblemente el sistema operativo, puede querer saber si lo que su programa debía hacer se realizó con éxito o no. Por esta misma razón, debe devolver cero o EXIT_SUCCESS
para indicar que el programa finalizó correctamente y EXIT_FAILURE
para indicar que el programa finalizó sin éxito, cualquier otra forma de valor de retorno está definida por la implementación ( §18.5 / 8 ).
Sin embargo, puede ser muy profundo en la pila de llamadas, y devolver todo puede ser doloroso ...
[No] lanzar una excepción
Lanzar una excepción llevará a cabo la limpieza adecuada del objeto utilizando el desbobinado de la pila, llamando al destructor de cada objeto en cualquier ámbito anterior.
Pero aquí está el truco ! Está definido por la implementación si el desbobinado de la pila se realiza cuando una excepción lanzada no se maneja (mediante la cláusula catch (...)) o incluso si tiene una noexcept
función en el medio de la pila de llamadas. Esto se establece en §15.5.1 [excepto.terminal] :
En algunas situaciones, se debe abandonar el manejo de excepciones para técnicas de manejo de errores menos sutiles. [Nota: Estas situaciones son:
[...]
- cuando el mecanismo de manejo de excepciones no puede encontrar un controlador para una excepción lanzada (15.3), o cuando la búsqueda de un controlador (15.3) encuentra el bloque más externo de una función con una noexcept
especificación- que no permite la excepción (15.4), o [...]
[...]
En tales casos, se llama std :: terminate () (18.8.3). En la situación en la que no se encuentra un controlador coincidente, se define la implementación si la pila se desenrolla o no antes de llamar a std :: terminate () [...]
¡Entonces tenemos que atraparlo!
¡Lanza una excepción y atrapala en main!
Dado que las excepciones no detectadas pueden no realizar el desbobinado de la pila (y, en consecuencia, no realizarán la limpieza adecuada) , debemos detectar la excepción en main y luego devolver un estado de salida ( EXIT_SUCCESS
o EXIT_FAILURE
).
Entonces, una configuración posiblemente buena sería:
int main()
{
/* ... */
try
{
// Insert code that will return by throwing a exception.
}
catch(const std::exception&) // Consider using a custom exception type for intentional
{ // throws. A good idea might be a `return_exception`.
return EXIT_FAILURE;
}
/* ... */
}
[No] std :: salir
Esto no realiza ningún tipo de desbobinado de la pila, y ningún objeto vivo en la pila llamará a su respectivo destructor para realizar la limpieza.
Esto se aplica en §3.6.1 / 4 [basic.start.init] :
Terminar el programa sin abandonar el bloque actual (por ejemplo, llamando a la función std :: exit (int) (18.5)) no destruye ningún objeto con una duración de almacenamiento automática (12.4) . Si se llama a std :: exit para finalizar un programa durante la destrucción de un objeto con una duración de almacenamiento estático o de subprocesos, el programa tiene un comportamiento indefinido.
Piénsalo ahora, ¿por qué harías tal cosa? ¿Cuántos objetos has dañado dolorosamente?
Otras alternativas [como malas]
Hay otras formas de finalizar un programa (que no sean fallas) , pero no se recomiendan. Solo en aras de una aclaración, se presentarán aquí. Observe cómo la finalización normal del programa no significa el desenrollado de la pila, sino un estado correcto para el sistema operativo.
std::_Exit
provoca una terminación normal del programa, y eso es todo.
std::quick_exit
provoca una finalización normal del programa y llama a los std::at_quick_exit
controladores, no se realiza ninguna otra limpieza.
std::exit
provoca una finalización normal del programa y luego llama a los std::atexit
controladores. Se realizan otros tipos de limpieza, como llamar a destructores de objetos estáticos.
std::abort
provoca una terminación anormal del programa, no se realiza ninguna limpieza. Esto debería llamarse si el programa terminó de una manera muy, muy inesperada. No hará nada más que indicarle al sistema operativo sobre la terminación anormal. Algunos sistemas realizan un volcado de núcleo en este caso.
std::terminate
llama a las llamadas std::terminate_handler
que std::abort
por defecto.
main()
retorno de uso, en funciones use un valor de retorno adecuado o arroje una Excepción adecuada. No lo useexit()
!