Dynamic exception specification (until C++17)
Énumère les exceptions qu'une fonction peut lever directement ou indirectement.
Table des matières |
Syntaxe
throw(
type-id-list
(optionnel)
)
|
(1) |
(obsolète en C++11)
(supprimé en C++17) |
|||||||
| type-id-list | - | liste séparée par des virgules de type-ids , un type-id représentant une pack expansion est suivi par des points de suspension (...) (depuis C++11) |
Une spécification d'exception dynamique explicite ne doit apparaître que sur un déclarateur de fonction pour un type de fonction, un pointeur vers un type de fonction, une référence vers un type de fonction, ou un pointeur vers un type de fonction membre qui est le type de niveau supérieur d'une déclaration ou définition, ou sur un tel type apparaissant comme paramètre ou type de retour dans un déclarateur de fonction.
void f() throw(int); // OK : déclaration de fonction void (*pf)() throw (int); // OK : déclaration de pointeur vers fonction void g(void pfa() throw(int)); // OK : déclaration de paramètre pointeur vers fonction typedef int (*pf)() throw(int); // Erreur : déclaration typedef
Explication
Si une fonction est déclarée avec un type
T
listé dans sa spécification d'exception dynamique, la fonction peut lever des exceptions de ce type ou d'un type qui en dérive.
Types incomplets
, les pointeurs ou références vers des types incomplets autres que cv
void*
, et les types de références rvalue
(depuis C++11)
ne sont pas autorisés dans la spécification d'exception. Les types tableau et fonction, s'ils sont utilisés, sont ajustés vers les types pointeur correspondants, les qualifications cv de niveau supérieur sont également supprimées.
les packs de paramètres
sont autorisés
(depuis C++11)
.
Une spécification d'exception dynamique dont l'ensemble des types ajustés est vide (après expansion des packs) (depuis C++11) est non-lancante. Une fonction avec une spécification d'exception dynamique non-lancante n'autorise aucune exception.
Une spécification d'exception dynamique n'est pas considérée comme faisant partie du type d'une fonction.
Si la fonction lance une exception d'un type non listé dans sa spécification d'exception, la fonction std::unexpected est appelée. La fonction par défaut appelle std::terminate , mais elle peut être remplacée par une fonction fournie par l'utilisateur (via std::set_unexpected ) qui peut appeler std::terminate ou lancer une exception. Si l'exception lancée depuis std::unexpected est acceptée par la spécification d'exception, le déroulement de la pile se poursuit normalement. Si elle ne l'est pas, mais que std::bad_exception est autorisée par la spécification d'exception, std::bad_exception est lancée. Sinon, std::terminate est appelé.
Instanciation
La spécification d'exception dynamique d'une spécialisation de fonction template n'est pas instanciée avec la déclaration de fonction ; elle est instanciée uniquement quand nécessaire (tel que défini ci-dessous).
La spécification d'exception dynamique d'une fonction membre spéciale implicitement déclarée n'est évaluée que lorsque nécessaire (en particulier, la déclaration implicite d'une fonction membre d'une classe dérivée ne nécessite pas l'instanciation de la spécification d'exception d'une fonction membre de base).
Lorsque la spécification d'exception dynamique d'une spécialisation de fonction template est nécessaire , mais n'a pas encore été instanciée, les noms dépendants sont recherchés et tous les templates utilisés dans l' expression sont instanciés comme pour la déclaration de la spécialisation.
Une spécification d'exception dynamique d'une fonction est considérée comme nécessaire dans les contextes suivants :
- dans une expression, où la fonction est sélectionnée par résolution de surcharge
- la fonction est odr-used
- la fonction serait odr-used mais apparaît dans un opérande non évalué
template<class T> T f() throw(std::array<char, sizeof(T)>); int main() { decltype(f<void>()) *p; // f non évaluée, mais la spécification d'exception est nécessaire // erreur car l'instanciation de la spécification d'exception // calcule sizeof(void) }
- la spécification est nécessaire pour comparer à une autre déclaration de fonction (par exemple, sur un surchargeur de fonction virtuelle ou sur une spécialisation explicite d'un modèle de fonction)
- dans une définition de fonction
- la spécification est nécessaire car une fonction membre spéciale par défaut doit la vérifier afin de décider de sa propre spécification d'exception (cela se produit uniquement lorsque la spécification de la fonction membre spéciale par défaut est, elle-même, nécessaire).
Exceptions potentielles
Chaque fonction
f
, pointeur vers fonction
pf
, et pointeur vers fonction membre
pmf
possède un
ensemble d'exceptions potentielles
, qui contient les types susceptibles d'être levés. L'ensemble de tous les types indique que n'importe quelle exception peut être levée. Cet ensemble est défini comme suit :
f
,
pf
, ou
pmf
utilise une spécification d'exception dynamique
qui n'autorise pas toutes les exceptions
(jusqu'à C++11)
, l'ensemble se compose des types listés dans cette spécification.
| (depuis C++11) |
Note : pour les fonctions membres spéciales implicitement déclarées (constructeurs, opérateurs d'affectation et destructeurs) et pour les constructeurs d'héritage (depuis C++11) , l'ensemble des exceptions potentielles est une combinaison des ensembles des exceptions potentielles de tout ce qu'elles appelleraient : constructeurs/opérateurs d'affectation/destructeurs des membres de données non statiques non variants, bases directes, et, le cas échéant, bases virtuelles (incluant les expressions d'arguments par défaut, comme toujours).
Chaque expression
e
possède un
ensemble d'exceptions potentielles
. L'ensemble est vide si
e
est une
expression constante de base
, sinon, c'est l'union des ensembles d'exceptions potentielles de toutes les sous-expressions immédiates de
e
(incluant les
expressions d'arguments par défaut
), combinée avec un autre ensemble qui dépend de la forme de
e
, comme suit :
e
est une expression d'appel de fonction, soit
g
la fonction, le pointeur de fonction ou le pointeur vers une fonction membre qui est appelé, alors
-
-
si la déclaration de
gutilise une spécification d'exception dynamique, l'ensemble des exceptions potentielles degest ajouté à l'ensemble ;
-
si la déclaration de
|
(depuis C++11) |
-
- sinon, l'ensemble est l'ensemble de tous les types.
e
appelle une fonction implicitement (c'est une expression d'opérateur et l'opérateur est surchargé, c'est une
new-expression
et la fonction d'allocation est surchargée, ou c'est une expression complète et le destructeur d'un temporaire est appelé), alors l'ensemble est l'ensemble de cette fonction.
e
est une
throw-expression
, l'ensemble est l'exception qui serait initialisée par son opérande, ou l'ensemble de tous les types pour la throw-expression de re-lancement (sans opérande).
e
est un
dynamic_cast
vers une référence d'un type polymorphe, l'ensemble se compose de
std::bad_cast
.
e
est un
typeid
appliqué à un pointeur déréférencé vers un type polymorphe, l'ensemble se compose de
std::bad_typeid
.
|
6)
Si
e
est une
new-expression
avec une taille de tableau non constante, et que la fonction d'allocation sélectionnée possède un ensemble non vide d'exceptions potentielles, l'ensemble contient
std::bad_array_new_length
.
|
(depuis C++11) |
void f() throw(int); // l'ensemble de f() est "int" void g(); // l'ensemble de g() est l'ensemble de tous les types struct A { A(); }; // l'ensemble de "new A" est l'ensemble de tous les types struct B { B() noexcept; }; // l'ensemble de "B()" est vide struct D() { D() throw (double); }; // l'ensemble de new D est l'ensemble de tous les types
Toutes les fonctions membres implicitement déclarées et les constructeurs hérités (depuis C++11) ont des spécifications d'exception, sélectionnées comme suit :
- Si l'ensemble des exceptions potentielles est l'ensemble de tous les types, la spécification d'exception implicite autorise toutes les exceptions (la spécification d'exception est considérée comme présente, même si elle est inexprimable dans le code et se comporte comme s'il n'y avait pas de spécification d'exception) (jusqu'en C++11) est noexcept ( false ) (depuis C++11) .
- Sinon, si l'ensemble des exceptions potentielles n'est pas vide, la spécification d'exception implicite énumère chaque type de l'ensemble.
- Sinon, la spécification d'exception implicite est throw ( ) (jusqu'en C++11) noexcept ( true ) (depuis C++11) .
struct A { A(int = (A(5), 0)) noexcept; A(const A&) throw(); A(A&&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) = default; // la spécification d'exception est "noexcept(true)" B(B&&, int = (throw Y(), 0)) noexcept; ~B() throw(Y); }; int n = 7; struct D : public A, public B { // Peut lever une exception d'un type qui correspondrait à un gestionnaire de type // std::bad_array_new_length, mais ne lève pas d'exception de mauvaise allocation (void*) new (std::nothrow) int[n]; // D peut avoir les membres implicitement déclarés suivants : // D::D() throw(X, std::bad_array_new_length); // D::D(const D&) noexcept(true); // D::D(D&&) throw(Y); // D::~D() throw(X, Y); };
Notes
Clang considère que la règle d'instanciation des spécifications d'exception dynamique est modifiée en C++11 par CWG1330 , voir LLVM #56349 .
Mots-clés
Exemple
Note : il est préférable de compiler en mode C++98 pour éviter les avertissements. Incompatible avec C++17 et les révisions plus récentes.
#include <cstdlib> #include <exception> #include <iostream> class X {}; class Y {}; class Z : public X {}; class W {}; void f() throw(X, Y) { bool n = false; if (n) throw X(); // OK, would call std::terminate() if (n) throw Z(); // also OK throw W(); // will call std::unexpected() } void handler() { std::cerr << "That was unexpected!\n"; // flush needed std::abort(); } int main() { std::set_unexpected(handler); f(); }
Sortie :
That was unexpected!
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 25 | C++98 |
le comportement de l'affectation et de l'initialisation
entre pointeurs vers membres avec différentes spécifications d'exception n'était pas spécifié |
appliquer la restriction
pour les pointeurs de fonction et les références |
| CWG 973 | C++98 |
la spécification d'exception pouvait contenir des types de fonctions, mais la
conversion correspondante des pointeurs de fonction n'était pas spécifiée |
spécifiée |
| CWG 1330 | C++98 | une spécification d'exception pouvait être instanciée de manière anticipée | elle n'est instanciée que si nécessaire |
| CWG 1267 | C++11 | les types de référence rvalue étaient autorisés dans les spécifications d'exception | non autorisés |
| CWG 1351 |
C++98
C++11 |
l'argument par défaut (C++98) et l'initialiseur de membre par défaut
(C++11) étaient ignorés dans la spécification d'exception implicite |
pris en compte |
| CWG 1777 | C++11 |
throw
(
T...
)
n'était pas une spécification non-lancante
même si T est un pack vide |
elle est non-lancante
si le pack est vide |
| CWG 2191 | C++98 |
l'ensemble des exceptions potentielles d'une expression
typeid
pouvait contenir
bad_typeid
même si elle ne peut pas être lancée
|
contient
bad_typeid
seulement si elle peut être lancée |
Voir aussi
noexcept
specifier
(C++11)
|
spécifie si une fonction peut lever des exceptions |