Namespaces
Variants

Handling 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

catch handler
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

Une exception peut être gérée par un gestionnaire.

Table des matières

Gestionnaire

catch ( attr  (optionnel) type-specifier-seq declarator ) compound-statement (1)
catch ( attr  (optionnel) type-specifier-seq abstract-declarator  (optionnel) ) compound-statement (2)
catch ( ... ) compound-statement (3)
1) Un gestionnaire avec un paramètre nommé.
2) Un gestionnaire avec un paramètre sans nom.
3) Un gestionnaire correspondant à tous types d'exceptions.
attr - (since C++11) n'importe quel nombre d' attributs , s'applique au paramètre
type-specifier-seq - partie d'une déclaration de paramètre formel, identique à celle d'une liste de paramètres de fonction
declarator - partie d'une déclaration de paramètre, identique à celle d'une liste de paramètres de fonction
abstract-declarator - partie d'une déclaration de paramètre sans nom, identique à celle d'une liste de paramètres de fonction
compound-statement - une instruction composée


La déclaration de paramètre dans un gestionnaire décrit le(s) type(s) d'exceptions qui peuvent provoquer l'entrée dans ce gestionnaire.

Si le paramètre est déclaré avoir l'un des types suivants, le programme est mal formé :

(depuis C++11)
  • un pointeur vers un type incomplet autre que (éventuellement qualifié cv) void
  • une référence lvalue vers un type incomplet

Si le paramètre est déclaré de type « tableau de T » ou de type fonction T , le type est ajusté en « pointeur vers T ».

Un gestionnaire avec un type de paramètre T peut être abrégé en « gestionnaire de type T ».

Correspondance des exceptions

Chaque try bloc est associé à plusieurs gestionnaires, ces gestionnaires forment une séquence de gestionnaires. Lorsqu'une exception est levée depuis un try bloc, les gestionnaires de la séquence sont essayés dans l'ordre d'apparition pour correspondre à l'exception.

Un gestionnaire correspond à un objet exception de type E si l'une des conditions suivantes est satisfaite :

  • Le gestionnaire est de type « T éventuellement qualifié cv » ou « référence à lvalue vers T éventuellement qualifié cv », et l'une des conditions suivantes est satisfaite :
  • E et T sont du même type (en ignorant les qualificateurs cv de niveau supérieur).
  • T est une classe de base publique non ambiguë de E .
  • Le gestionnaire est de type « T éventuellement qualifié cv » T ou const T & T est un type pointeur ou pointeur-sur-membre, et l'une des conditions suivantes est satisfaite :
  • E est un type pointeur ou pointeur-vers-membre qui peut être converti en T par au moins l'une des conversions suivantes :
(depuis C++17)
(depuis C++11)

Le gestionnaire catch ( ... ) intercepte les exceptions de tout type. S'il est présent, il ne peut être que le dernier gestionnaire dans une séquence de gestionnaires. Ce gestionnaire peut être utilisé pour garantir qu'aucune exception non interceptée ne puisse s'échapper d'une fonction qui offre une garantie d'absence d'exception .

try
{
    f();
}
catch (const std::overflow_error& e)
{} // cette clause s'exécute si f() lance std::overflow_error (règle du même type)
catch (const std::runtime_error& e)
{} // cette clause s'exécute si f() lance std::underflow_error (règle de la classe de base)
catch (const std::exception& e)
{} // cette clause s'exécute si f() lance std::logic_error (règle de la classe de base)
catch (...)
{} // cette clause s'exécute si f() lance std::string ou int ou tout autre type non apparenté

Si aucune correspondance n'est trouvée parmi les gestionnaires d'un bloc try , la recherche d'un gestionnaire correspondant se poursuit dans un bloc try englobant dynamiquement du même thread (since C++11) .

Si aucun gestionnaire correspondant n'est trouvé, std::terminate est invoqué ; que la pile soit ou non unwound avant cette invocation de std::terminate est défini par l'implémentation.

Gestion des exceptions

Lorsqu'une exception est levée, le contrôle est transféré vers le gestionnaire le plus proche avec un type correspondant ; « le plus proche » signifie le gestionnaire pour lequel l'instruction composée ou la liste d'initialisation des membres (si présente) suivant le mot-clé try a été le plus récemment entré par le fil d'exécution et n'a pas encore été quitté.

Initialisation du paramètre handler

Le paramètre déclaré dans la liste des paramètres (le cas échéant), de type « T éventuellement qualifié cv » ou « référence à lvalue vers T éventuellement qualifié cv », est initialisé à partir de l' objet exception , de type E , comme suit :

  • Si T est une classe de base de E , le paramètre est copy-initialized à partir d'une lvalue de type T désignant le sous-objet de classe de base correspondant de l'objet exception.
  • Sinon, le paramètre est copy-initialized à partir d'une lvalue de type E désignant l'objet exception.

La durée de vie du paramètre se termine lorsque le gestionnaire se termine, après la destruction de tout objet ayant une durée de stockage automatique initialisé dans le gestionnaire.

Lorsque le paramètre est déclaré comme un objet, toute modification de cet objet n'affectera pas l'objet d'exception.

Lorsque le paramètre est déclaré comme une référence à un objet, toute modification de l'objet référencé constitue une modification de l'objet d'exception et aura un effet si cet objet est relancé.

Activation du gestionnaire

Un gestionnaire est considéré comme actif lorsque l'initialisation est terminée pour le paramètre (le cas échéant) du gestionnaire.

De plus, un gestionnaire implicite est considéré comme actif lorsque std::terminate est appelé suite à un lancer d'exception.

Un gestionnaire n'est plus considéré comme actif lorsque le gestionnaire se termine.

L'exception avec le gestionnaire le plus récemment activé qui est toujours actif est appelée l'exception actuellement gérée . Une telle exception peut être relancée .

Flux de contrôle

La compound-statement d'un gestionnaire est une instruction à flux de contrôle limité :

void f()
{
    goto label;     // erreur
    try
    {
        goto label; // erreur
    }
    catch (...)
    {
        goto label: // OK
        label: ;
    }
}

Notes

Stack unwinding se produit pendant que le contrôle est transféré à un gestionnaire. Lorsqu'un gestionnaire devient actif, le déroulement de pile est déjà terminé.

L'exception levée par l'expression throw throw 0 ne correspond pas à un gestionnaire de type pointeur ou pointeur-vers-membre.

  • throw nullptr peut être utilisé à la place pour lever un pointeur nul qui correspond à de tels gestionnaires.
(depuis C++11)

Objets d'exception ne peuvent jamais avoir de types tableau ou fonction, par conséquent un gestionnaire de référence à un type tableau ou fonction ne correspond jamais à aucun objet d'exception.

Il est possible d'écrire des gestionnaires qui ne pourront jamais être exécutés, par exemple en plaçant un gestionnaire pour une classe dérivée finale après un gestionnaire pour une classe de base publique non ambiguë correspondante :

try
{
    f();
}
catch (const std::exception& e)
{} // sera exécuté si f() lance std::runtime_error
catch (const std::runtime_error& e)
{} // code mort !

De nombreuses implémentations étendent excessivement la résolution de CWG issue 388 aux gestionnaires de référence vers des types pointeur non-const :

int i;
try
{
    try
    {
        throw static_cast<float*>(nullptr);
    }
    catch (void*& pv)
    {
        pv = &i;
        throw;
    }
}
catch (const float* pf)
{
    assert(pf == nullptr); // devrait passer, mais échoue sur MSVC et Clang
}

Mots-clés

catch

Exemple

L'exemple suivant démontre plusieurs cas d'utilisation des gestionnaires :

#include <iostream>
#include <vector>
int main()
{
    try
    {
        std::cout << "Throwing an integer exception...\n";
        throw 42;
    }
    catch (int i)
    {
        std::cout << " the integer exception was caught, with value: " << i << '\n';
    }
    try
    {
        std::cout << "Creating a vector of size 5... \n";
        std::vector<int> v(5);
        std::cout << "Accessing the 11th element of the vector...\n";
        std::cout << v.at(10); // vector::at() throws std::out_of_range
    }
    catch (const std::exception& e) // caught by reference to base
    {
        std::cout << " a standard exception was caught, with message: '"
                  << e.what() << "'\n";
    }
}

Sortie possible :

Throwing an integer exception...
 the integer exception was caught, with value: 42
Creating a vector of size 5...
Accessing the 11th element of the vector...
 a standard exception was caught, with message: 'out_of_range'

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 tel que publié Comportement correct
CWG 98 C++98 une instruction switch peut transférer le contrôle vers un gestionnaire interdit
CWG 210 C++98 les expressions throw étaient comparées aux gestionnaires les objets d'exception sont
comparés aux gestionnaires
CWG 388 C++98 une exception de type pointeur ou pointeur vers membre ne
pouvait pas être comparée par une référence const vers un type différent
rendue comparable
lorsqu'elle est convertible
CWG 1166 C++98 le comportement n'était pas spécifié lorsqu'un gestionnaire dont
le type est une référence vers un type de classe abstraite est comparé
les types de classes abstraites ne sont
pas autorisés pour les gestionnaires
CWG 1769 C++98 lorsque le type du gestionnaire est une base du type de
l'objet d'exception, un constructeur de conversion pourrait
être utilisé pour l'initialisation du paramètre du gestionnaire
le paramètre est initialisé par copie
à partir de la sous-classe de base correspondante
du sous-objet de l'objet d'exception
CWG 2093 C++98 un objet d'exception de type pointeur vers objet ne pouvait pas correspondre à un
gestionnaire de type pointeur vers objet via une conversion de qualification
autorisé

Références

  • Norme C++23 (ISO/CEI 14882:2024) :
  • 14.4 Gestion d'une exception [except.handle]
  • Norme C++20 (ISO/CEI 14882:2020) :
  • 14.4 Gestion d'une exception [except.handle]
  • Norme C++17 (ISO/CEI 14882:2017) :
  • 18.3 Gestion d'une exception [except.handle]
  • Norme C++14 (ISO/CEI 14882:2014) :
  • 15.3 Gestion d'une exception [except.handle]
  • Norme C++11 (ISO/IEC 14882:2011) :
  • 15.3 Gestion d'une exception [except.handle]
  • Norme C++03 (ISO/CEI 14882:2003) :
  • 15.3 Gestion d'une exception [except.handle]
  • Norme C++98 (ISO/CEI 14882:1998) :
  • 15.3 Gestion d'une exception [except.handle]

Voir aussi