Throwing exceptions
Lancer une exception transfère le contrôle à un handler .
Une exception peut être levée depuis throw expressions , les contextes suivants peuvent également lever des exceptions :
Table des matières |
Objet d'exception
Le lancement d'une exception initialise un objet avec une durée de stockage dynamique, appelé objet d'exception .
Si le type de l'objet exception serait l'un des types suivants, le programme est mal formé :
- un type incomplet
- un type de classe abstraite
- un pointeur vers un type incomplet autre que void (éventuellement qualifié cv)
Construction et destruction des objets d'exception
Étant donné le type de l'objet d'exception comme
T
:
-
Soit
obj
une lvalue de type
const
T
, la
copy-initialization
d'un objet de type
Tà partir de obj doit être valide. -
Si
Test un type classe :
-
- Le constructeur sélectionné est odr-used .
-
Le
destructeur
de
Test potentially invoked .
La mémoire pour l'objet exception est allouée de manière non spécifiée. La seule garantie est que le stockage ne sera jamais alloué par les fonctions d'allocation globales .
Si un handler se termine par une rethrow , le contrôle est transmis à un autre handler pour le même objet d'exception. L'objet d'exception n'est pas détruit dans ce cas.
|
Lorsque le dernier gestionnaire actif restant pour l'exception se termine par tout moyen autre qu'un rethrow, l'objet d'exception est détruit et l'implémentation peut désallouer la mémoire de l'objet temporaire de manière non spécifiée. La destruction se produit immédiatement après la destruction de l'objet déclaré dans la "liste de paramètres" du gestionnaire. |
(jusqu'à C++11) |
|
Les points de destruction potentiels pour l'objet d'exception sont :
Parmi tous les points de destruction potentiels pour l'objet d'exception, il existe un dernier non spécifié où l'objet d'exception est détruit. Tous les autres points se produisent avant ce dernier. L'implémentation peut ensuite désallouer la mémoire pour l'objet d'exception de manière non spécifiée. |
(depuis C++11) |
throw expressions
throw
expression
|
(1) | ||||||||
throw
|
(2) | ||||||||
| expression | - | l'expression utilisée pour construire l'objet exception |
Lorsqu'une nouvelle exception est levée, son objet d'exception est déterminé comme suit :
- Les conversions standard array-to-pointer et function-to-pointer sont effectuées sur expression .
- Soit ex le résultat de la conversion :
-
-
- Le type de l'objet exception est déterminé en supprimant tous les qualificateurs cv de plus haut niveau du type de ex .
- L'objet exception est copy-initialized à partir de ex .
-
Si un programme tente de relancer une exception lorsqu'aucune exception n'est actuellement traitée, std::terminate sera invoqué. Sinon, l'exception est réactivée avec l'objet d'exception existant (aucun nouvel objet d'exception n'est créé), et l'exception n'est plus considérée comme étant capturée.
try { // lancer une nouvelle exception 123 throw 123; } catch (...) // attraper toutes les exceptions { // répondre (partiellement) à l'exception 123 throw; // transmettre l'exception à un autre gestionnaire }
Déroulement de la pile
Une fois l'objet exception construit, le flux de contrôle remonte (dans la pile d'appels) jusqu'à atteindre le début d'un try block , moment auquel les paramètres de tous les gestionnaires associés sont comparés, dans l'ordre d'apparition, avec le type de l'objet exception pour trouver une correspondance . Si aucune correspondance n'est trouvée, le flux de contrôle continue à dérouler la pile jusqu'au prochain try block, et ainsi de suite. Si une correspondance est trouvée, le flux de contrôle saute vers le gestionnaire correspondant.
Au fur et à mesure que le flux de contrôle remonte la pile d'appels, les destructeurs sont invoqués pour tous les objets ayant une durée de stockage automatique qui sont construits mais pas encore détruits, depuis que le bloc try correspondant a été entré, dans l'ordre inverse de l'achèvement de leurs constructeurs. Si une exception est levée depuis un destructeur d'une variable locale ou d'un temporaire utilisé dans une instruction return , le destructeur de l'objet retourné par la fonction est également invoqué.
Si une exception est levée depuis un constructeur ou (rarement) depuis un destructeur d'un objet (quel que soit la durée de stockage de l'objet), les destructeurs sont appelés pour tous les membres non-statiques non-variants entièrement construits et les classes de base, dans l'ordre inverse de l'achèvement de leurs constructeurs. Les membres variants des classes de type union sont seulement détruits dans le cas de déroulement depuis le constructeur, et si le membre actif change entre l'initialisation et la destruction, le comportement est indéfini.
|
Si un constructeur délégué se termine avec une exception après que le constructeur non déléguant s'est terminé avec succès, le destructeur pour cet objet est appelé. |
(depuis C++11) |
Si l'exception est levée depuis un constructeur invoqué par une new-expression , la fonction de désallocation correspondante est appelée, si elle est disponible.
Ce processus est appelé stack unwinding .
Si une fonction appelée directement par le mécanisme de déroulement de la pile, après l'initialisation de l'objet exception et avant le début du gestionnaire d'exception, se termine avec une exception, std::terminate est appelé. Ces fonctions incluent les destructeurs des objets à durée de stockage automatique dont les portées sont quittées, et le constructeur de copie de l'objet exception qui est appelé (s'il n'est pas élidé ) pour initialiser les arguments de capture par valeur.
Si une exception est levée et non attrapée, y compris les exceptions qui échappent à la fonction initiale de std::thread , la fonction main, et le constructeur ou destructeur de tout objet statique ou thread-local, alors std::terminate est appelé. Il est défini par l'implémentation si un déroulement de pile a lieu pour les exceptions non attrapées.
Notes
Lors du relancement d'exceptions, la seconde forme doit être utilisée pour éviter le découpage d'objet dans le cas (typique) où les objets d'exception utilisent l'héritage :
try { std::string("abc").substr(10); // lève std::out_of_range } catch (const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // initialise par copie un nouvel objet exception de type std::exception throw; // relance l'objet exception de type std::out_of_range }
L'expression throw est classifiée comme une expression prvalue de type void . Comme toute autre expression, elle peut être une sous-expression dans une autre expression, le plus souvent dans l' opérateur conditionnel :
double f(double d) { return d > 1e7 ? throw std::overflow_error("trop grand") : d; } int main() { try { std::cout << f(1e10) << '\n'; } catch (const std::overflow_error& e) { std::cout << e.what() << '\n'; } }
Mots-clés
Exemple
#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(); }
Sortie :
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
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 499 | C++98 |
un tableau de taille inconnue ne pouvait pas être lancé car
son type est incomplet, mais un objet exception peut être créé à partir du pointeur dégradé sans aucun problème |
appliquer l'exigence de complétion de type
à l'objet exception à la place |
| CWG 668 | C++98 |
std::terminate
n'était pas appelé si une exception est lancée
depuis le destructeur d'un objet local non-automatique |
appeler
std::terminate
dans ce cas |
| CWG 1863 | C++11 |
le constructeur de copie n'était pas requis pour les objets
d'exception uniquement déplaçables lors du lancement, mais la copie autorisée ultérieurement |
constructeur de copie requis |
| CWG 1866 | C++98 | les membres variant étaient divulgués lors du déroulement de pile depuis le constructeur | membres variant détruits |
| CWG 2176 | C++98 |
le lancement depuis un destructeur de variable locale
pouvait ignorer le destructeur de valeur de retour |
valeur de retour de fonction
ajoutée au déroulement |
| CWG 2699 | C++98 | throw "EX" lançait en réalité char * plutôt que const char * | corrigé |
| CWG 2711 | C++98 |
la source de la copy-initialization de
l'objet exception n'était pas spécifiée |
initialisé par copie
depuis expression |
| CWG 2775 | C++98 | l'exigence de copy-initialization de l'objet exception était peu claire | clarifiée |
| CWG 2854 | C++98 | la durée de stockage des objets exception était peu claire | clarifiée |
| P1825R0 | C++11 |
le déplacement implicite depuis les paramètres était interdit dans
throw
|
autorisé |
Références
- Norme C++23 (ISO/CEI 14882:2024) :
-
- 7.6.18 Lancement d'une exception [expr.throw]
-
- 14.2 Lancement d'une exception [except.throw]
- Norme C++20 (ISO/CEI 14882:2020) :
-
- 7.6.18 Lancement d'une exception [expr.throw]
-
- 14.2 Lancement d'une exception [except.throw]
- Norme C++17 (ISO/CEI 14882:2017) :
-
- 8.17 Lancement d'une exception [expr.throw]
-
- 18.1 Lancement d'une exception [except.throw]
- Norme C++14 (ISO/CEI 14882:2014) :
-
- 15.1 Lancement d'une exception [except.throw]
- Norme C++11 (ISO/CEI 14882:2011) :
-
- 15.1 Lancement d'une exception [except.throw]
- Norme C++03 (ISO/IEC 14882:2003) :
-
- 15.1 Lancement d'une exception [except.throw]
- Norme C++98 (ISO/CEI 14882:1998) :
-
- 15.1 Lancement d'une exception [except.throw]
Voir aussi
| (jusqu'à C++17) |