Namespaces
Variants

Throwing 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
throw -expression
try block
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

Lanzar una excepción transfiere el control a un manejador .

Una excepción puede ser lanzada desde throw expressions , los siguientes contextos también pueden lanzar excepciones:

Contenidos

Objeto de excepción

Lanzar una excepción inicializa un objeto con storage duration dinámico, llamado exception object .

Si el tipo del objeto de excepción sería uno de los siguientes tipos, el programa está mal formado:

Construcción y destrucción de objetos de excepción

Dado el tipo del objeto de excepción como T :

  • Sea obj un lvalue de tipo const T , la inicialización por copia de un objeto de tipo T desde obj debe estar bien formada.
  • Si T es un tipo clase:

La memoria para el objeto de excepción se asigna de manera no especificada. La única garantía es que el almacenamiento nunca será asignado por las funciones de asignación global .

Si un handler termina mediante rethrowing , el control se pasa a otro handler para el mismo objeto de excepción. El objeto de excepción no se destruye en este caso.

Cuando el último manejador activo restante para la excepción sale por cualquier medio que no sea relanzar, el objeto de excepción es destruido y la implementación puede desasignar la memoria para el objeto temporal de manera no especificada.

La destrucción ocurre inmediatamente después de la destrucción del objeto declarado en la "lista de parámetros" en el manejador.

(until C++11)

Los puntos de destrucción potencial para el objeto de excepción son:

  • Cuando un manejador activo para la excepción sale por cualquier medio que no sea relanzar, inmediatamente después de la destrucción del objeto (si existe) declarado en la "lista de parámetros" en el manejador.
  • Cuando un objeto de tipo std::exception_ptr que hace referencia al objeto de excepción es destruido, antes de que el destructor de std::exception_ptr retorne.

Entre todos los puntos de destrucción potencial para el objeto de excepción, hay un último no especificado donde el objeto de excepción es destruido. Todos los otros puntos happen before ese último. La implementación puede entonces desasignar la memoria para el objeto de excepción de manera no especificada.

(since C++11)

throw expresiones throw

throw expresión (1)
throw (2)
1) Lanza una nueva excepción.
2) Vuelve a lanzar la excepción que se está manejando actualmente.
expression - la expresión utilizada para construir el objeto de excepción


Cuando se lanza una nueva excepción, su objeto de excepción se determina de la siguiente manera:

  1. Se realizan las conversiones estándar de array-a-puntero y función-a-puntero sobre expression .
  2. Sea ex el resultado de la conversión:
  • El tipo del objeto de excepción se determina eliminando cualquier calificador cv de nivel superior del tipo de ex .
  • El objeto de excepción es copy-initialized desde ex .

Si un programa intenta relanzar una excepción cuando actualmente no se está manejando ninguna excepción, std::terminate será invocado. De lo contrario, la excepción se reactiva con el objeto de excepción existente (no se crea ningún nuevo objeto de excepción), y la excepción ya no se considera capturada.

try
{
    // lanzando una nueva excepción 123
    throw 123;
}
catch (...) // capturar todas las excepciones
{
    // responder (parcialmente) a la excepción 123
    throw; // pasar la excepción a otro manejador
}

Desenrollado de pila

Una vez que el objeto de excepción es construido, el flujo de control retrocede (hacia arriba en la pila de llamadas) hasta que alcanza el inicio de un try bloque , momento en el cual los parámetros de todos los manejadores asociados son comparados, en orden de aparición, con el tipo del objeto de excepción para encontrar una coincidencia . Si no se encuentra ninguna coincidencia, el flujo de control continúa desenrollando la pila hasta el siguiente try bloque, y así sucesivamente. Si se encuentra una coincidencia, el flujo de control salta al manejador correspondiente.

A medida que el flujo de control asciende por la pila de llamadas, se invocan los destructores para todos los objetos con duración de almacenamiento automático que fueron construidos pero aún no destruidos, desde que se ingresó al bloque try correspondiente, en orden inverso al de finalización de sus constructores. Si se lanza una excepción desde un destructor de una variable local o de un temporal utilizado en una sentencia return , también se invoca el destructor del objeto devuelto por la función.

Si se lanza una excepción desde un constructor o (rara vez) desde un destructor de un objeto (independientemente de la duración de almacenamiento del objeto), se llaman los destructores para todos los miembros no estáticos no variantes completamente construidos y clases base, en orden inverso a la finalización de sus constructores. Los miembros variantes de union-like classes solo se destruyen en caso de desenrollado desde el constructor, y si el miembro activo cambió entre la inicialización y la destrucción, el comportamiento es indefinido.

Si un constructor delegado sale con una excepción después de que el constructor no delegado se complete exitosamente, se llama al destructor para este objeto.

(since C++11)

Si la excepción es lanzada desde un constructor que es invocado por una new-expression , la función de desasignación correspondiente deallocation function es llamada, si está disponible.

Este proceso se denomina stack unwinding .

Si cualquier función que es llamada directamente por el mecanismo de desenrollado de pila, después de la inicialización del objeto de excepción y antes del inicio del manejador de excepciones, termina con una excepción, std::terminate es llamado. Tales funciones incluyen destructors de objetos con duración de almacenamiento automático cuyos ámbitos son abandonados, y el copy constructor del objeto de excepción que es llamado (si no es elided ) para inicializar argumentos catch-by-value.

Si se lanza una excepción y no es capturada, incluyendo excepciones que escapan de la función inicial de std::thread , la función principal, y el constructor o destructor de cualquier objeto estático o thread-local, entonces std::terminate es llamado. Es definido por la implementación si ocurre algún desenrollado de pila (stack unwinding) para excepciones no capturadas.

Notas

Al relanzar excepciones, debe usarse la segunda forma para evitar el object slicing en el caso (típico) donde los objetos de excepción utilizan 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 por copia un nuevo objeto de excepción de tipo std::exception
    throw;   // relanza el objeto de excepción de tipo std::out_of_range
}

La throw -expression se clasifica como 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

throw

Ejemplo

#include <iostream>
#include <stdexcept>
struct A
{
    int n;
    A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; }
    ~A() { std::cout << "A(" << n << ") destroyed\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 constructed successfully\n";
    }
    catch(...)
    {
        std::cout << "B::B() exiting with exception\n";
    }
    ~B() { std::cout << "B destroyed\n"; }
};
struct C : A, B
{
    C() try
    {
        std::cout << "C::C() completed successfully\n";
    }
    catch(...)
    {
        std::cout << "C::C() exiting with exception\n";
    }
    ~C() { std::cout << "C destroyed\n"; }
};
int main () try
{
    // creates the A base subobject
    // creates the a1 member of B
    // fails to create the a2 member of B
    // unwinding destroys the a1 member of B
    // unwinding destroys the A base subobject
    C c;
}
catch (const std::exception& e)
{
    std::cout << "main() failed to create C with: " << e.what();
}

Salida:

A(0) constructed successfully
A(1) constructed successfully
A(1) destroyed
B::B() exiting with exception
A(0) destroyed
C::C() exiting with exception
main() failed to create C with: error

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Se aplica a Comportamiento publicado Comportamiento correcto
CWG 499 C++98 un array con límite desconocido no podía lanzarse porque
su tipo está incompleto, pero un objeto de excepción puede
crearse a partir del puntero decayado sin ningún problema
aplicar el requisito de completitud de tipo
al objeto de excepción en su lugar
CWG 668 C++98 std::terminate no se llamaba si se lanza una excepción
desde el destructor de un objeto local no automático
llamar a std::terminate
en este caso
CWG 1863 C++11 el constructor de copia no era requerido para objetos de excepción
de solo movimiento al lanzarlos, pero se permitía copiar después
constructor de copia requerido
CWG 1866 C++98 los miembros variant se filtraban en el desenrollado de pila desde el constructor miembros variant destruidos
CWG 2176 C++98 lanzar desde un destructor de variable local
podría omitir el destructor del valor de retorno
valor de retorno de función
añadido al desenrollado
CWG 2699 C++98 throw "EX" en realidad lanzaría char * en lugar de const char * corregido
CWG 2711 C++98 no se especificaba la fuente de la inicialización por copia
del objeto de excepción
inicializado por copia
desde expresión
CWG 2775 C++98 el requisito de inicialización por copia del objeto de excepción no estaba claro aclarado
CWG 2854 C++98 la duración de almacenamiento de los objetos de excepción no estaba clara aclarado
P1825R0 C++11 el movimiento implícito desde parámetros estaba prohibido en throw permitido

Referencias

  • Estándar C++23 (ISO/IEC 14882:2024):
  • 7.6.18 Lanzamiento de una excepción [expr.throw]
  • 14.2 Lanzamiento de una excepción [except.throw]
  • Estándar C++20 (ISO/IEC 14882:2020):
  • 7.6.18 Lanzamiento de una excepción [expr.throw]
  • 14.2 Lanzamiento de una excepción [except.throw]
  • Estándar C++17 (ISO/IEC 14882:2017):
  • 8.17 Lanzamiento de una excepción [expr.throw]
  • 18.1 Lanzamiento de una excepción [except.throw]
  • Estándar C++14 (ISO/IEC 14882:2014):
  • 15.1 Lanzamiento de una excepción [except.throw]
  • Estándar C++11 (ISO/IEC 14882:2011):
  • 15.1 Lanzamiento de una excepción [except.throw]
  • Estándar C++03 (ISO/IEC 14882:2003):
  • 15.1 Lanzamiento de una excepción [except.throw]
  • Estándar C++98 (ISO/IEC 14882:1998):
  • 15.1 Lanzamiento de una excepción [except.throw]

Véase también

(hasta C++17)