Namespaces
Variants

Handling exceptions

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions

catch handler
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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)
1) Un manejador con un parámetro nombrado.
2) Un manejador con un parámetro sin nombre.
3) Un manejador que coincide con todos los tipos de excepciones.
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 cv T ”, y se satisface cualquiera de las siguientes condiciones:
  • E y T son el mismo tipo (ignorando los calificadores cv de nivel superior).
  • T es una clase base pública no ambigua de E .
  • El manejador es de tipo “posiblemente calificado-cv T ” o const T & donde T es un tipo puntero o puntero-a-miembro, y se satisface alguna de las siguientes condiciones:
  • E es un tipo puntero o puntero-a-miembro que puede convertirse a T mediante al menos una de las siguientes conversiones:
(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 T es una clase base de E , el parámetro es copy-initialized desde un lvalue de tipo T que 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 E que 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.

  • throw nullptr puede utilizarse en su lugar para lanzar un puntero nulo que coincida con dichos manejadores.
(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

catch

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]

Véase también