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

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é :

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 T est un type classe :

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 :

  • Lorsqu'un gestionnaire actif pour l'exception se termine par tout moyen autre qu'un rethrow, immédiatement après la destruction de l'objet (le cas échéant) déclaré dans la "liste de paramètres" du gestionnaire.
  • Lorsqu'un objet de type std::exception_ptr qui référence l'objet d'exception est détruit, avant que le destructeur de std::exception_ptr ne retourne.

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)
1) Lance une nouvelle exception.
2) Renvoie l'exception actuellement en cours de traitement.
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 :

  1. Les conversions standard array-to-pointer et function-to-pointer sont effectuées sur expression .
  2. 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

throw

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)