Espacios de nombres
Variantes
Acciones

Expresión throw

De cppreference.com
< cpp‎ | language

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)
1) Primero, inicializa usando inicialización de copia el objeto excepción a partir de la expresión.
  • Esto puede llamar al constructor de movimiento para una expresión rvalue. Incluso si la inicialización de copia selecciona el constructor de movimiento, la inicialización de copia desde lvalue debe estar bien formada y el destructor debe ser accesible.
(desde C++11)
  • Esto también puede llamar al constructor de movimiento para expresiones que denominan a variables locales o a parámetros de funciones o de cláusulas catch cuyo ámbito no se extienda más allá del bloque try que lo rodea (si es que lo hay), por la misma resolución de sobrecarga que en la instrucción return
(desde C++17)


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.
2) Vuelve a lanzar la excepción que se acaba de controlar. Abandona la ejecución del bloque 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

throw

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

Véase también