Cómo finalizar el código de C ++

Me gustaría que mi código C ++ deje de funcionar si se cumple una determinada condición, pero no estoy seguro de cómo hacerlo. Entonces, en cualquier punto si una statement if es verdadera, termine el código así:

 if (x==1) { kill code; } 

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 progtwigdores de C ++.

RAII y Stack Unwinding

C ++ hace uso de una expresión idiomática llamada 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, std::ofstream class [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 scope, se llama al destructor que esencialmente cierra el archivo y vacía cualquier contenido escrito en el disco.

¿Qué sucede si no se llega al destructor para descargar y cerrar el archivo? ¡Quién sabe! Pero posiblemente no escriba todos los datos que debía escribir en el archivo.

Por ejemplo, considere este código

 #include  #include  #include  void inner_mad() { throw std::exception(); } void mad() { std::unique_ptr ptr(new 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: Retorno esencialmente deja el scope de la función actual, por lo que sabe sobre el final del ciclo de vida de os llamando así a su destructor y haciendo una limpieza adecuada cerrando y vaciando el archivo en el disco.
  • Posibilidad 2: lanzar una excepción también se ocupa del ciclo de vida de los objetos en el scope actual, realizando una limpieza adecuada ...
  • Posibilidad 3: ¡ aquí el desenrollado de la stack entra en acción! A pesar de que la excepción se produce en inner_mad , el inner_mad pasará por la stack de mad y main para realizar una limpieza adecuada, todos los objetos se destruirán correctamente, incluidos ptr y os .
  • Posibilidad 4: Bueno, aquí? exit es una función C y no es compatible ni compatible con las expresiones idiomáticas de C ++. No realiza limpieza en sus objetos, incluido el sistema os en el mismo ámbito. Por lo tanto, su archivo no se cerrará correctamente y, por este motivo, es posible que el contenido nunca se incluya en él.
  • Otras posibilidades: simplemente saldrá del scope principal, realizando un return 0 implícito return 0 y teniendo así el mismo efecto que la posibilidad 1, es decir, una limpieza adecuada.

Pero no estés tan seguro de lo que acabo de contarte (principalmente las posibilidades 2 y 3); Continúa leyendo y descubriremos cómo realizar una limpieza basada en excepciones adecuada.

Posibles formas de terminar

Regreso de main!

Debe hacer esto siempre que sea posible; siempre prefiere regresar de su progtwig devolviendo un estado de salida correcto desde main.

La persona que llama de su progtwig, y ​​posiblemente el sistema operativo, podría querer saber si lo que se suponía que debía hacer su progtwig se había realizado correctamente o no. Por esta misma razón, debe devolver cero o EXIT_SUCCESS para indicar que el progtwig finalizó exitosamente y EXIT_FAILURE para indicar que el progtwig terminó sin éxito, cualquier otra forma de valor de retorno está definida por la implementación ( §18.5 / 8 ).

Sin embargo, puede que estés muy metido en la stack de llamadas, y devolver todo eso puede ser doloroso ...

[No] lanza una excepción

Al lanzar una excepción se realizará una limpieza adecuada del objeto utilizando el desenrollado de la stack, llamando al destructor de cada objeto en cualquier ámbito anterior.

¡Pero aquí está la trampa ! Se define en la implementación si se desenrolla la stack cuando no se maneja una excepción arrojada (por la cláusula catch (...)) o incluso si tiene una función noexcept en el medio de la stack de llamadas. Esto se establece en §15.5.1 [excepto.terminado] :

  1. 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 arrojada (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 que no permite la excepción (15.4), o [...]

    [...]

  2. En tales casos, se llama a std :: terminate () (18.8.3). En la situación en la que no se encuentra ningún controlador coincidente, está definido por la implementación si la stack se desenrolla o no antes de llamar a std :: terminate () [...]

¡Entonces tenemos que atraparlo!

¡Lanza una excepción y tómalo en main!

Debido a que las excepciones no detectadas pueden no desenrollar la stack (y por lo tanto no realizarán la limpieza adecuada) , debemos capturar 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; } /* ... */ } 

[Do not] std :: exit

Esto no realiza ningún tipo de desenrollado de stack, y ningún objeto vivo en la stack llamará a su destructor respectivo para realizar la limpieza.

Esto se aplica en §3.6.1 / 4 [basic.start.init] :

Terminar el progtwig sin salir del bloque actual (por ejemplo, llamando a la función std :: exit (int) (18.5)) no destruye ningún objeto con duración de almacenamiento automático (12.4) . Si se llama a std :: exit para finalizar un progtwig durante la destrucción de un objeto con duración estática o de almacenamiento de subprocesos, el progtwig tiene un comportamiento indefinido.

Piénselo ahora, ¿por qué haría tal cosa? ¿Cuántos objetos has dañado dolorosamente?

Otras alternativas [tan malas]

Hay otras maneras de finalizar un progtwig (que no sea la falla) , pero no son recomendables. Solo por el bien de la aclaración, se presentarán aquí. Observe cómo la terminación normal del progtwig no significa que la stack se desenrolla, sino un estado aceptable para el sistema operativo.

  • std::_Exit causa una terminación normal del progtwig, y ​​eso es todo.
  • std::quick_exit causa una terminación de progtwig normal y llama a los controladores std::at_quick_exit , no se realiza ninguna otra limpieza.
  • std::exit causa una terminación normal del progtwig y luego llama a los controladores std::atexit . Se realizan otros tipos de limpiezas, como llamar a destructores de objetos estáticos.
  • std::abort causa una terminación anormal del progtwig, no se realiza ninguna limpieza. Esto debe invocarse si el progtwig termina de una manera realmente inesperada. No hará más que indicar al sistema operativo acerca de la terminación anormal. Algunos sistemas realizan un volcado de núcleo en este caso.
  • std::terminate llama a std::terminate_handler que llama a std::abort por defecto.

Como mencionó Martin York, la salida no realiza la limpieza necesaria como lo hace el retorno.

Siempre es mejor usar el retorno en el lugar de salida. En caso de que no esté en la posición principal, donde quiera salir del progtwig, regrese primero a la línea principal.

Considera el siguiente ejemplo. Con el siguiente progtwig, se creará un archivo con el contenido mencionado. Pero si return es una salida comentada y no comentada (0), el comstackdor no le asegura que el archivo tendrá el texto requerido.

 int main() { ofstream os("out.txt"); os < < "Hello, Can you see me!\n"; return(0); //exit(0); } 

No solo esto, tener múltiples puntos de salida en un progtwig hará que la depuración sea más difícil. Use exit solo cuando pueda justificarse.

Llame a la función std::exit .

La gente dice “salir de la llamada (código de retorno)”, pero esta es una mala forma. En progtwigs pequeños, está bien, pero hay varios problemas con esto:

  1. Terminará teniendo múltiples puntos de salida del progtwig
  2. Hace que el código sea más intrincado (como usar goto)
  3. No puede liberar memoria asignada en tiempo de ejecución

Realmente, el único momento en que deberías salir del problema es con esta línea en main.cpp:

 return 0; 

Si está utilizando exit () para manejar los errores, debe conocer las excepciones (y las excepciones de anidación), como un método mucho más elegante y seguro.

return 0; ponlo donde quieras en int main() y el progtwig se cerrará inmediatamente.

O devuelve un valor de su main o usa la función de exit . Ambos toman un int. Realmente no importa qué valor devuelva a menos que tenga un proceso externo que esté atento al valor de retorno.

El progtwig terminará cuando el flujo de ejecución llegue al final de la función principal.

Para finalizarlo antes, puede usar la función exit (int status), donde el estado es un valor devuelto a cualquier cosa que haya iniciado el progtwig. 0 normalmente indica un estado sin error

Si tiene un error en alguna parte del código, genere una excepción o establezca el código de error. Siempre es mejor lanzar una excepción en lugar de establecer códigos de error.

En general, usaría el método exit() con un estado de salida apropiado.

Zero significaría una carrera exitosa. Un estado distinto de cero indica que se ha producido algún tipo de problema. Este código de salida es utilizado por procesos principales (por ejemplo, scripts de shell) para determinar si un proceso se ha ejecutado correctamente.

Más allá de llamar a exit (código_error), que llama a los controladores atexit, pero no a los destructores RAII, etc., cada vez uso más excepciones.

Cada vez más mi progtwig principal se ve como

 int main(int argc, char** argv) { try { exit( secondary_main(argc, argv ); } catch(...) { // optionally, print something like "unexpected or unknown exception caught by main" exit(1); } } 

donde secondary_main en donde se pone todo lo que originalmente se puso, es decir, el main original se renombra secondary_main, y se agrega el stub main anterior. Esto es solo una maravilla, por lo que no hay demasiado código entre la bandeja y la captura en main.

Si lo desea, capture otros tipos de excepciones.
Me gusta mucho capturar los tipos de error de cadena, como std :: string o char *, e imprimir esos en el controlador catch en main.

El uso de excepciones como esta al menos permite que se invoquen destructores RAII, para que puedan hacer la limpieza. Que puede ser agradable y útil.

En general, el manejo de errores C – exit y signal – y el manejo de errores C ++ – try / catch / throw exceptions – juegan juntos de forma inconsistente en el mejor de los casos.

Entonces, donde detecta un error

 throw "error message" 

o algún tipo de excepción más específico.

Para romper una condición use el retorno (0);

Entonces, en tu caso sería:

  if(x==1) { return 0; } 

Si su statement if está en Loop, puede usar

  break; 

Si quieres escapar de algún código y continuar haciendo un bucle, utiliza:

continuar;

Si su statement if no está en Loop, puede usar:

  return 0; Or exit(); 

La función … de tipo de exit() se define bajo stdlib.h

Entonces necesita agregar un preprocesador.

Poner include stdlib.h en la sección de encabezado

Luego use exit(); donde quiera, pero recuerde poner un número interger entre paréntesis de salida.

por ejemplo:

 exit(0); 

Si la condición que estoy probando es realmente una mala noticia, hago esto:

 *(int*) NULL= 0; 

Esto me da un buen núcleo de donde puedo examinar la situación.