Expresión throw
Indica una condición errónea y ejecuta un controlador de errores.
Contenido |
Sintaxis
throw expresión
|
(1) | ||||||||
throw
|
(2) | ||||||||
Explicación
- Véase bloque try-catch para más información sobre los bloques try y catch (controlador de excepción)
|
(desde C++11) |
|
(desde C++17) |
- La copia /o movimiento (desde C++11) puede estar sujeta(o) a la elisión de copia.
- entonces transfiere el control al controlador de excepción con el tipo coincidente para el cual la instrucción compuesta o lista de inicializadores de miembros que sigue a la palabra clave try se ingresó más recientemente y no salió de este hilo de ejecución.
catch
actual y pasa el control al siguiente controlador de excepción coincidente (pero no a otra cláusula catch
posterior al mismo bloque try: esta instrucción compuesta se considera que ha 'salido') reutilizando el objeto excepción existente: no se crean nuevos objetos. Esta forma se permite solamente cuando una excepción se está controlando (llama a std::terminate si se usa de otra forma). La cláusula catch
asociada con un bloque try
de función tiene que salir volviendo a lanzar si se usa en un constructor.Véase std::terminate y std::unexpected (hasta C++17) para el control de errores que surgen durante el control de excepciones.
El objeto excepción
El objeto excepción es un objeto temporal en almacenamiento inespecífico que se construye mediante la expresión throw
.
El tipo del objeto excepción es el tipo estático de la expresión con los calificadores-cv superiores eliminados. Los arrays y tipos función se ajustan a tipos puntero y puntero a función, respectivamente. Si el tipo del objeto excepción fuese un tipo incompleto, un tipo clase abstracta, o un puntero a un tipo incompleto distinto de un puntero a void (calificado-cv), la expresión throw
es un error en tiempo de compilación. Si el tipo de la expresión es un tipo clase, su constructor de copia/movimiento (desde C++11) y su destructor deben ser accesibles aun si la elisión de copia se efectúa.
A diferencia de otros objetos temporales, el objeto excepción se considera que es un argumento lvalue al inicializar los parámetros de la cláusula catch
, de tal manera que pueda ser atrapado por referencia lvalue, modificado y vuelto a lanzar.
El objeto excepción persiste hasta que la última cláusula catch
termina, excepto si se vuelve a lanzar (si no se vuelve a lanzar, se destruye inmediatamente después de la destrucción del parámetro de la cláusula catch), o hasta que el último std::exception_ptr que referencia a este objeto se destruye (en cuyo caso el objeto excepción se destruye justo antes de que el destructor de std::exception_ptr retorne).
Desenredo de pila
Una vez que se construye el objeto excepción, el flujo de control se revierte (hacia la parte superior de la pila de llamadas) hasta que alcanza el inicio de un bloque try, en cuyo punto los parámetros de todos los bloques catch
asociados se comparan, en orden de aparición, con el tipo del objeto excepción para encontrar una coincidencia (véase try-catch para más detalles de este proceso). Si no se encuentra una coincidencia, el flujo de control continúa desenredando la pila hasta el próximo bloque try
, y así sucesivamente. Si se encuentra una coincidencia, el flujo de control salta al bloque catch
coincidente.
A medida que el flujo de control se mueve hacia la parte superior de la pila de llamadas, se invocan los destructores de todos los objetos con duración de almacenamiento automática que se construyeron, pero que todavía no se destruyen, desde que se ingresó al bloque try
correspondiente, en orden inverso del que se completaron sus constructores. Si se lanza una excepción desde un destructor de una variable local o de un temporal usada en una instrucción return, también se invoca al destructor del objeto devuelto por la función. (desde C++14)
Si se lanza una excepción desde un constructor o (raramente) desde un destructor de un objeto (independientemente de la duración de almacenamiento del objeto), se llama a los destructores para todos(as) los miembro y las clases base, no estáticos(as), no variantes y completamente construidos(as), en orden inverso del que se completaron sus constructores. Los miembros variantes de clases tipo unión se destruyen solamente en el caso de desenredo desde un constructor, y si el miembro activo cambió entre la inicialización y destrucción, el comportamiento es indefinido. (desde C++14)
Si un constructor delegado sale con una excepción después de que el constructor no delegado se completó con éxito, se llama al destructor de este objeto. | (desde C++11) |
Si la excepción se lanza desde un constructor que se invoca por una expresión new, se llama a la función de desasignación de memoria coincidente, si está disponible.
Este proceso se llama desenredo de pila.
Si cualquier función que se llama directamente por el mecanismo de desenredo de pila, después de la inicialización del objeto excepción y antes del inicio del controlador de excepción, sale con una excepción, se llama a std::terminate. Tales funciones incluyen: destructores de objetos con duración de almacenamiento automática de cuyos ámbitos se sale, y el constructor de copia del objeto excepción que se llama (si no ha sido elidido) para inicializar argumentos para atrapar por valor.
Si se lanza una excepción y no se atrapa, incluyendo excepciones que se escapan de la función inicial de std::thread, la función main
, y el constructor o destructor de cualquier objeto estático o local al hilo, entonces se llama a std::terminate. Está definido por la implementación si se efectúa algún desenredo de pila para las excepciones que no se atraparon.
Notas
Al volver a lanzar excepciones, se tiene que usar la segunda forma para evitar división de objetos en el caso (típico) donde los objetos excepción usen herencia:
try { std::string("abc").substr(10); // lanza std::out_of_range } catch(const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // inicializa con inicialización de copia un objeto excepción nuevo // de tipo std::exception throw; // vuelve a lanzar el objeto excepción de tipo std::out_of_range }
La expresión throw
se clasifica como una expresión prvalue de tipo void. Como cualquier otra expresión, puede ser una subexpresión en otra expresión, más comúnmente en el operador condicional:
double f(double d) { return d > 1e7 ? throw std::overflow_error("demasiado grande") : d; } int main() { try { std::cout << f(1e10) << '\n'; } catch (const std::overflow_error& e) { std::cout << e.what() << '\n'; } }
Palabras clave
Ejemplo
#include <iostream> #include <stdexcept> struct A { int n; A(int n = 0): n(n) { std::cout << "A(" << n << ") construido con exito\n"; } ~A() { std::cout << "A(" << n << ") destruido\n"; } }; int foo() { throw std::runtime_error("error"); } struct B { A a1, a2, a3; B() try : a1(1), a2(foo()), a3(3) { std::cout << "B construido con éxito\n"; } catch(...) { std::cout << "B::B() salida con excepción\n"; } ~B() { std::cout << "B destruido\n"; } }; struct C : A, B { C() try { std::cout << "C::C() completo con éxito\n"; } catch(...) { std::cout << "C::C() salida con excepción\n"; } ~C() { std::cout << "C destruido\n"; } }; int main () try { // crea el subobjeto base A // crea el miembro a1 de B // falla al crear el miembro a2 de B // el desenredo destruye el miembro a1 de B // el desenredo destruye el subobjeto base de A C c; } catch (const std::exception& e) { std::cout << "main() fallo al crear C con: " << e.what(); }
Salida:
A(0) construido con éxito A(1) construido con éxito A(1) destruido B::B() salida con excepción A(0) destruido C::C() salida con excepción main() fallo al crear C con: error
Informes 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 499 | C++98 | no se podía lanzar un array de límites desconocido porque su tipo es incompleto, pero se puede crear un objeto excepción a partir del puntero corrupto sin ningún problema |
se aplica el requisito de tipo completo al objeto de excepción en su lugar |
CWG 668 | C++98 | no se llamaba a std::terminate si se lanza una excepción desde el destructor de un objeto local no automático |
se llama a std::terminate en este caso |
CWG 1863 | C++11 | no se requería constructor de copia para los objetos excepción de solo-movimiento al ser lanzados, pero se permitía la copia más adelante |
se requiere constructor de copia |
CWG 1866 | C++98 | hubo fugas de miembros variantes durante el desenredo de pila del constructor |
miembros variantes destruidos |
CWG 2176 | C++98 | lanzar desde el destructor de una una variable local podía saltarse el destructor del valor de retorno |
el valor de retorno de la función se añadió al desenredo de pila |