Especificador noexcept (desde C++11)
Especifica si una función podría lanzar una excepción.
Sintaxis
noexcept
|
(1) | ||||||||
noexcept(expresión)
|
(2) | ||||||||
throw()
|
(3) | (en desuso en C++17) (eliminado en C++20) | |||||||
noexcept(true)true, la función se declara que no lanza ninguna excepción. Un ( a continuación de noexcept siempre es parte de esta forma (nunca puede empezar un inicializador).noexcept(true) (véase la especificación de excepción dinámica para conocer su semántica antes de C++17).| expresión | - | expresión constante convertida contextualmente de tipo bool.
|
Explicación
|
La especificación void f() noexcept; // la función f() no lanza excepciones
void (*fp)() noexcept(false); // fp apunta a una función que puede lanzar excepciones
void g(void pfa() noexcept); // g toma un puntero a función que no lanza excepciones
// typedef int (*pf)() noexcept; // ERROR
|
(hasta C++17) |
|
La especificación |
(desde C++17) |
Toda función en C++ es bien que no lanza o que potencialmente lanza.
- Las funciones que potencialmente lanzan son:
|
(hasta C++17) |
- las funciones declaradas con el especificador
noexceptcuya expresión se evalúa afalse; - las funciones declaradas sin el especificador
noexceptexcepto:
- los destructores, a menos que el destructor de cualquier base o miembro potencialmente construido pueda potencialmente lanzar (véase abajo);
- los constructores por defecto, constructores de copia, constructores de movimiento que estén declarados implícitamente o marcados con
defaulten su primera declaración a menos que:
- un constructor de una base o miembro que la definición implícita del constructor llamaría, potencialmente lance (véase abajo);
- una subexpresión de tal inicialización, tal como una expresión de argumento por defecto, potencialmente lance(véase abajo);
- un inicializador de miembro por defecto (solamente para un constructor por defecto) potencialmente lance(véase abajo).
- los operadores de asignación de copia y asignación de movimiento que son declarados implícitamente o marcados con
defaulten su primer declaración, a menos que la invocación de cualquier operador de asignación en la definición implícita potencialmente lance (véase abajo);
- las funciones declaradas con el especificador
|
(desde C++20) |
- las funciones que no lanzan son el resto (todas aquellas con un especificador
noexceptcuya expresión se evalúa atrue, así como destructores, funciones miembro especiales marcadas condefault, y funciones de desasignación de memoria).
La instanciación explícita de objetos puede usar el especificador noexcept, pero no se requiere. Si se usa, la especificación de excepción tiene que ser la misma para todas las otras declaraciones. Se requiere un diagnóstico solamente si las especificaciones de excepción no son las mismas dentro de una sola unidad de traducción.
Las funciones que difieren solamente en su especificación de excepción no pueden sobrecargarse (similar al tipo de retorno, la especificación de excepción es parte del tipo de la función, pero no parte de la signatura de la función) (desde C++17).
void f() noexcept;
void f(); // ERROR: especificación de excepción distinta
void g() noexcept(false);
void g(); // de acuerdo, ambas declaraciones de g potencialmente lanzan
Los punteros (incluyendo punteros a función miembro) a funciones que no lanzan pueden asignarse o usarse para incializar (hasta C++17)son convertibles implícitamente a (desde C++17) punteros a función que potencialmente lancen, pero no al revés.
void ft(); // potencialmente lanza
void (*fn)() noexcept = ft; // ERROR
Si una función virtual no lanza, todas las declaraciones, incluyendo la definición, de cada reemplazador tampoco deben lanzar, a menos que el reemplazador esté marcado con delete:
struct B
{
virtual void f() noexcept;
virtual void g();
virtual void h() noexcept = delete;
};
struct D: B
{
void f(); // mal formado: D::f potencialmente lanza, B::f no lanza
void g() noexcept; // de acuerdo
void h() = delete; // de acuerdo
};
Se permite que las funciones que no lanzan llamen a funciones que potencialmente lanzan. Siempre que se lance una excepción y la búsqueda de un controlador de excepción encuentre el bloque más externo de una función que no lanza, se llama a la función std::terminate:
extern void f(); // potencialmente lanza
void g() noexcept
{
f(); // válido, incluso si f lanza
throw 42; // válido, en realidad una llamada a std::terminate
}
La especificación de excepción de una especialización de una plantilla de función no genera una instancia junto con la declaración de la función; se genera solamente cuando se necesita (como se define abajo).
La especificación de excepción de una función miembro especial declarada implícitamente también se evalúa solamente cuando se necesita (en particular, la declaración implícita de una función miembro de una clase derivada no requiere que se genere una instancia de la especificación de excepción de una función miembro de la clase base).
Cuando se necesita la especificación noexcept de una especialización de una plantilla de función, pero todavía no se ha creado una instancia, se buscan los nombres dependientes y se crea una instancia de cualquier plantilla usada en la expresión como si fueran parte de la declaración de la especialización.
Una especificación noexcept de una función se considera necesaria en los siguientes contextos:
- en una expresión donde la función se selecciona mediante resolución de sobrecarga;
- la función es de uso ODR;
- la función sería de uso ODR, pero aparece en un operando no evaluado;
template<class T>
T f() noexcept(sizeof(T) < 4);
int main()
{
decltype(f<void>()) *p; // f no evaluada, pero se necesita especificación noexcept
// ERROR debido a la instanciación de la especificación noexcept
// calcula sizeof(void)
}
- se necesita la especificación para compararla con la declaración de otra función (p. ej., en un reemplazador de una función virtual o en una especialización explícita de una plantilla de función);
- en una definición de función;
- la especificación se necesita porque una función miembro especial marcada con
defaultnecesita revisarla para decidir su propia especificación de excepción (esto tiene lugar solamente cuando la especificación de la función miembro especial marcada condefaultes, a su vez, necesaria).
La definición formal de una expresión que potencialmente lanza (utilizada para determinar la especificación de excepción por defecto de destructores, constructores, y operadores de asignación, como se describe arriba) es:
Una expresión e potencialmente lanza si:
ees una llamada de función a una función, un puntero a función o puntero a una función miembro que potencialmente lanza, a menos queesea una expresión constante central (hasta C++17);ehace una llamada implícita a una función que potencialmente lanza (tal como un operador sobrecargado, una función de asignación de memoria en una expresiónnew, un contructor para un argumento de función, o un destructor siees una expresión completa);ees una expresiónthrow;ees una conversión dinámica que convierte un tipo referencia polimórfico;ees una expresión typeid aplicada para desreferenciar un puntero a un tipo polimórfico;etiene una subexpresión inmediata que potencialmente lanza.
struct A
{
A(int = (A(5), 0)) noexcept;
A(const A&) noexcept;
A(A&&) noexcept;
~A();
};
struct B
{
B() throw();
B(const B&) = default; // especificación de excepción implícita es noexcept(true)
B(B&&, int = (throw Y(), 0)) noexcept;
~B() noexcept(false);
};
int n = 7;
struct D : public A, public B
{
int * p = new int[n];
// D::D() potencialmente lanza por causa del operador new
// D::D(const D&) no lanza
// D::D(D&&) potencialmente lanza: el arg. por defecto del ctor de B puede lanzar
// D::~D() potencialmente lanza
// nota: si A::~A() fuera virtual, este programa estaría mal formado porque un
// reemplazador de una fn virtual que no lanza no puede ser que potencialmente lance.
};
Notas
Uno de los usos de la expresión constante (junto con el operador noexcept) es definir las funciones de plantilla que declaran noexcept para algunos tipos pero no para otros.
Ten en cuenta que una especificación noexcept en una función no es una comprobación en tiempo de compilación; es simplemente un método usado por el programador para informar al compilador si una función debe o no lanzar excepciones. El compilador puede usar esta información para habilitar ciertas optimizaciones en funciones que no lanzan, así como para habilitar el operador noexcept, que puede comprobar en tiempo de compilación si una expresión particular está declarada para lanzar excepciones. Por ejemplo, contenedores tales como std::vector moverán sus elementos si el constructor de movimiento de los elementos es noexcept, y de otra manera copiarán (si el constructor de copia no está accesible, pero sí lo está un constructor de movimiento que potencialmente lanza, se usa este último y la garantía de excepción fuerte no se aplica).
En desuso
noexcept es una versión mejorada de throw(), que está en desuso desde C++11. A diferencia de throw() anterior a C++17, noexcept no llamará a std::unexpected, puede o no desenredar la pila, y llamará a std::terminate, lo que potencialmente permite al compilador implementar noexcept sin el costo en tiempo de ejecución de throw(). A partir de C++17, throw() se redefine como un equivalente exacto de noexcept(true).
| Macro de prueba de característica | Valor | Estándar | Comentario |
|---|---|---|---|
__cpp_noexcept_function_type |
201510L |
(C++17) | Hace que las especificaciones de excepción formen parte del sistema de tipos |
Palabras clave
noexcept, throw (desde C++17)(hasta C++20)
Ejemplo
// que foo se declare noexcept depende de si la expresión
// T() lanzará alguna excepción
template <class T>
void foo() noexcept(noexcept(T())) {}
void bar() noexcept(true) {}
void baz() noexcept { throw 42; } // noexcept es lo mismo que noexcept(true)
int main()
{
foo<int>(); // noexcept(noexcept(int())) => noexcept(true), esto está bien
bar(); // bien
baz(); // compila, pero en tiempo de ejecución esto llama a std::terminate
}
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 1330 | C++11 | Una especificación de excepción podría instanciarse vorazmente. | Se instancia solo si se necesita. |
| CWG 1740 | C++11 | Un ( que sigue a noexcept podría empezar un inicializador.
|
Solo puede ser parte de la especificación noexcept |
| CWG 2039 | C++11 | Solo se requería que la expresión antes de la conversión fuera constante. | La conversión también tiene que ser La conversión también debe ser válida en una expresión constante. |
Véase también
Operador noexcept
|
Determina si una expresión lanza alguna excepción. (desde C++11) |
| Especificación de excepción | Especifica qué excepciones se lanzan por una función (en desuso) |
Expresión throw
|
Indica un error y transfiere el control al controlador de error. |
(C++11) |
Obtiene una referencia r-valor si el constructor de movimiento no lanza una excepción (plantilla de función) |