Handling exceptions
Una excepción puede ser manejada por un manejador.
Contenidos |
Manejador
catch
(
attr
(opcional)
type-specifier-seq
declarator
)
compound-statement
|
(1) | ||||||||
catch
(
attr
(opcional)
type-specifier-seq
abstract-declarator
(opcional)
)
compound-statement
|
(2) | ||||||||
catch
(
...
)
compound-statement
|
(3) | ||||||||
| attr | - | (since C++11) cualquier número de attributes , se aplica al parámetro |
| type-specifier-seq | - | parte de una declaración de parámetro formal, igual que en una lista de parámetros de función |
| declarator | - | parte de una declaración de parámetro, igual que en una lista de parámetros de función |
| abstract-declarator | - | parte de una declaración de parámetro sin nombre, igual que en una lista de parámetros de función |
| compound-statement | - | una sentencia compuesta |
La declaración de parámetros en un manejador describe el tipo(s) de excepciones que pueden causar que ese manejador sea ingresado.
Si el parámetro se declara con uno de los siguientes tipos, el programa está mal formado:
|
(desde C++11) |
- un puntero a un tipo incompleto que no sea (posiblemente calificado cv) void
- una referencia lvalue a un tipo incompleto
Si el parámetro se declara con tipo "array de
T
" o tipo función
T
, el tipo se ajusta a "puntero a
T
".
Un manejador con tipo de parámetro
T
puede abreviarse como "un manejador de tipo
T
".
Coincidencia de excepciones
Cada try block se asocia con varios manejadores, estos manejadores forman una secuencia de manejadores. Cuando se lanza una excepción desde un try block, los manejadores en la secuencia se prueban en orden de aparición para coincidir con la excepción.
Un manejador es una coincidencia para un
objeto de excepción
de tipo
E
si se satisface cualquiera de las siguientes condiciones:
-
El manejador es de tipo “posiblemente calificado con cv
T” o “referencia a lvalue a posiblemente calificado con cvT”, y se satisface cualquiera de las siguientes condiciones:
-
-
EyTson el mismo tipo (ignorando los calificadores cv de nivel superior). -
Tes una clase base pública no ambigua deE.
-
-
El manejador es de tipo “posiblemente calificado-cv
T” o const T & dondeTes un tipo puntero o puntero-a-miembro, y se satisface alguna de las siguientes condiciones:
-
-
Ees un tipo puntero o puntero-a-miembro que puede convertirse aTmediante al menos una de las siguientes conversiones:
-
- Una conversión estándar de puntero que no involucre conversiones a punteros a clases privadas, protegidas o ambiguas.
-
| (desde C++17) |
|
(desde C++11) |
El catch ( ... ) handler coincide con excepciones de cualquier tipo. Si está presente, solo puede ser el último handler en una secuencia de handlers. Este handler puede utilizarse para garantizar que ninguna excepción no capturada pueda escapar de una función que ofrece nothrow exception guarantee .
try { f(); } catch (const std::overflow_error& e) {} // esto se ejecuta si f() lanza std::overflow_error (regla del mismo tipo) catch (const std::runtime_error& e) {} // esto se ejecuta si f() lanza std::underflow_error (regla de clase base) catch (const std::exception& e) {} // esto se ejecuta si f() lanza std::logic_error (regla de clase base) catch (...) {} // esto se ejecuta si f() lanza std::string o int o cualquier otro tipo no relacionado
Si no se encuentra ninguna coincidencia entre los manejadores para un bloque try , la búsqueda de un manejador coincidente continúa en un bloque try que lo rodea dinámicamente del mismo hilo (desde C++11) .
Si no se encuentra un manejador coincidente, std::terminate es invocado; si la pila es unwound antes de esta invocación de std::terminate está definido por la implementación.
Manejo de excepciones
Cuando se lanza una excepción, el control se transfiere al manejador más cercano con un tipo coincidente; "más cercano" significa el manejador para el cual la sentencia compuesta o la lista de inicialización de miembros (si está presente) que sigue a la try palabra clave fue ingresada más recientemente por el hilo de control y aún no ha sido salida.
Inicializando el parámetro handler
El parámetro declarado en la lista de parámetros (si existe), de tipo "posiblemente calificado cv
T
" o "referencia a lvalue a posiblemente calificado cv
T
", se inicializa desde el
objeto de excepción
, de tipo
E
, de la siguiente manera:
-
Si
Tes una clase base deE, el parámetro es copy-initialized desde un lvalue de tipoTque designa el subobjeto de clase base correspondiente del objeto de excepción. -
De lo contrario, el parámetro es copy-initialized desde un lvalue de tipo
Eque designa el objeto de excepción.
La duración del parámetro termina cuando el controlador sale, después de la destrucción de cualquier objeto con storage duration automática inicializado dentro del controlador.
Cuando el parámetro se declara como un objeto, cualquier cambio en ese objeto no afectará al objeto de excepción.
Cuando el parámetro se declara como una referencia a un objeto, cualquier cambio al objeto referenciado son cambios al objeto de excepción y tendrán efecto si ese objeto es relanzado.
Activando el handler
Un manejador se considera activo cuando la inicialización está completa para el parámetro (si existe) del manejador.
Además, se considera que un manejador implícito está activo cuando std::terminate es invocado debido a un lanzamiento de excepción.
Un controlador ya no se considera activo cuando el controlador sale.
La excepción con el manejador más recientemente activado que aún está activo se denomina excepción actualmente manejada . Dicha excepción puede ser relanzada .
Flujo de control
La compound-statement de un handler es una declaración de flujo de control limitado :
void f() { goto label; // error try { goto label; // error } catch (...) { goto label: // OK label: ; } }
Notas
Stack unwinding ocurre mientras el control se transfiere a un manejador. Cuando un manejador se activa, el stack unwinding ya está completado.
La excepción lanzada por la expresión throw throw 0 no coincide con un manejador de tipo puntero o puntero-a-miembro.
|
(desde C++11) |
Objetos de excepción nunca pueden tener tipos de arreglo o función, por lo tanto un manejador de referencia a tipo arreglo o función nunca coincide con ningún objeto de excepción.
Es posible escribir manejadores que nunca pueden ejecutarse, por ejemplo colocando un manejador para una clase derivada final después de un manejador para una clase base pública no ambigua correspondiente:
try { f(); } catch (const std::exception& e) {} // se ejecutará si f() lanza std::runtime_error catch (const std::runtime_error& e) {} // ¡código inalcanzable!
Muchas implementaciones extienden excesivamente la resolución de CWG issue 388 a manejadores de referencia a tipos de puntero no constante:
int i; try { try { throw static_cast<float*>(nullptr); } catch (void*& pv) { pv = &i; throw; } } catch (const float* pf) { assert(pf == nullptr); // debería pasar, pero falla en MSVC y Clang }
Palabras clave
Ejemplo
El siguiente ejemplo demuestra varios casos de uso de los manejadores:
#include <iostream> #include <vector> int main() { try { std::cout << "Throwing an integer exception...\n"; throw 42; } catch (int i) { std::cout << " the integer exception was caught, with value: " << i << '\n'; } try { std::cout << "Creating a vector of size 5... \n"; std::vector<int> v(5); std::cout << "Accessing the 11th element of the vector...\n"; std::cout << v.at(10); // vector::at() throws std::out_of_range } catch (const std::exception& e) // caught by reference to base { std::cout << " a standard exception was caught, with message: '" << e.what() << "'\n"; } }
Salida posible:
Throwing an integer exception... the integer exception was caught, with value: 42 Creating a vector of size 5... Accessing the 11th element of the vector... a standard exception was caught, with message: 'out_of_range'
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 98 | C++98 | una sentencia switch podía transferir control a un manejador | prohibido |
| CWG 210 | C++98 | las expresiones throw se emparejaban con los manejadores |
los objetos de excepción son
emparejados con los manejadores |
| CWG 388 | C++98 |
una excepción de tipo puntero o puntero a miembro no
podía ser emparejada por una referencia constante a un tipo diferente |
se permite el emparejamiento
cuando son convertibles |
| CWG 1166 | C++98 |
el comportamiento no estaba especificado cuando un manejador cuyo
tipo es una referencia a un tipo de clase abstracta es emparejado |
los tipos de clase abstracta no están
permitidos para manejadores |
| CWG 1769 | C++98 |
cuando el tipo del manejador es una base del tipo del
objeto de excepción, un constructor de conversión podría ser usado para la inicialización del parámetro del manejador |
el parámetro se inicializa por copia
desde la correspondiente subclase base del objeto de excepción |
| CWG 2093 | C++98 |
un objeto de excepción de tipo puntero a objeto no podía emparejarse con un
manejador de tipo puntero a objeto mediante conversión de calificación |
permitido |
Referencias
- Estándar C++23 (ISO/IEC 14882:2024):
-
- 14.4 Manejo de una excepción [except.handle]
- Estándar C++20 (ISO/IEC 14882:2020):
-
- 14.4 Manejo de una excepción [except.handle]
- Estándar C++17 (ISO/IEC 14882:2017):
-
- 18.3 Manejo de una excepción [except.handle]
- Estándar C++14 (ISO/IEC 14882:2014):
-
- 15.3 Manejo de una excepción [except.handle]
- Estándar C++11 (ISO/IEC 14882:2011):
-
- 15.3 Manejo de una excepción [except.handle]
- Estándar C++03 (ISO/IEC 14882:2003):
-
- 15.3 Manejo de una excepción [except.handle]
- Estándar C++98 (ISO/IEC 14882:1998):
-
- 15.3 Manejo de una excepción [except.handle]