Partager via


Spécifications d’exception (throw, noexcept) (C++)

Les spécifications d’exception sont une fonctionnalité du langage C++ qui indique l’intention du programmeur concernant les types d’exception qui peuvent être propagés par une fonction. Vous pouvez spécifier qu’une fonction peut ou non se terminer par une exception en utilisant une spécification d’exception. Le compilateur peut utiliser ces informations pour optimiser les appels à la fonction, et pour terminer le programme si une exception inattendue échappe à la fonction.

Avant C++17, il y avait deux types de spécification d’exception. La spécification noexcept a été introduite dans C++11. Elle spécifie si l’ensemble des exceptions potentielles pouvant échapper à la fonction est vide. La spécification d’exception dynamique, ou spécification throw(optional_type_list), est déconseillé en C++11, et a été supprimée en C++17, à l’exception de throw(), qui est un alias de noexcept(true). Cette spécification d’exception a été conçue pour fournir un résumé des exceptions qui peuvent être lancées depuis une fonction, mais en pratique elle s’est avérée problématique. La seule spécification d’exception dynamique qui s’est avérée quelque peu utile est la spécification throw() inconditionnelle. Par exemple, la déclaration de fonction :

void MyFunction(int i) throw();

indique au compilateur que la fonction ne lève pas d'exception. Cependant, en mode /std:c++14, cela pourrait entraîner un comportement indéfini si la fonction lance une exception. Par conséquent, nous recommandons d’utiliser l’opérateur noexcept à la place de celui mentionné ci-dessus :

void MyFunction(int i) noexcept;

Le tableau suivant résume l’implémentation des spécifications d’exception par Microsoft C++ :

Spécification d'exception Signification
noexcept
noexcept(true)
throw()
La fonction ne lève pas d'exception. En mode /std:c++14 (qui est le mode par défaut), noexcept et noexcept(true) sont équivalents. Lorsqu’une exception est lancée par une fonction déclarée noexcept ou noexcept(true), std::terminate est invoquée. Lorsqu’une exception est lancée par une fonction déclarée throw() en mode /std:c++14, le résultat est un comportement indéfini. Aucune fonction spécifique n’est invoquée. Cela diverge de la norme C++14, qui exigeait que le compilateur invoque std::unexpected.
Visual Studio 2017 version 15.5 et versions ultérieure : en mode /std:c++17, noexcept, noexcept(true) et throw() sont tous équivalents. En mode /std:c++17, throw() est un alias pour noexcept(true). En mode /std:c++17 et versions ultérieures, lorsqu’une exception est lancée par une fonction déclarée avec l’une de ces spécifications, std::terminate est invoqué conformément à la norme C++17.
noexcept(false)
throw(...)
Pas de spécification
La fonction peut lancer une exception de n’importe quel type.
throw(type) (C++14 et versions antérieures) La fonction peut lancer une exception de type type. Le compilateur accepte la syntaxe, mais l’interprète comme noexcept(false). En mode /std:c++17 et versions ultérieures, le compilateur émet l’avertissement C5040.

Si la gestion des exceptions est utilisée dans une application, il doit y avoir une fonction dans la pile d’appels qui gère les exceptions lancées avant qu’elles ne quittent l’étendue extérieure d’une fonction marquée noexcept, noexcept(true) ou throw(). Si des fonctions appelées entre celle qui lance une exception et celle qui la gère sont spécifiées comme noexcept, noexcept(true) (ou throw() en mode /std:c++17), le programme est terminé lorsque la fonction noexcept propage l’exception.

Le comportement d’exception d’une fonction dépend des facteurs suivants :

  • Quel mode de compilation de la norme du langage est défini.

  • si vous compilez la fonction sous C ou C++ ;

  • Quelle option de compilateur /EH vous utilisez.

  • si vous spécifiez de manière explicite la spécification d'exception.

Les spécifications d'exceptions explicites ne sont pas autorisées sur les fonctions C. Une fonction C est supposée ne pas lancer d’exceptions sous /EHsc, et peut lancer des exceptions structurées sous /EHs, /EHa ou /EHac.

Le tableau suivant résume si une fonction C++ peut potentiellement lancer une exception selon différentes options de gestion d’exception du compilateur :

Fonction /EHsc /EHs /EHa /EHac
Fonction C++ sans spécification d'exception Oui Oui Oui Oui
Fonction C++ avec spécification d’exception noexcept, noexcept(true) ou throw() Non Non Oui Oui
Fonction C++ avec spécification d’exception noexcept(false), throw(...) ou throw(type) Oui Oui Oui Oui

Exemple

// exception_specification.cpp
// compile with: /EHs
#include <stdio.h>

void handler() {
   printf_s("in handler\n");
}

void f1(void) throw(int) {
   printf_s("About to throw 1\n");
   if (1)
      throw 1;
}

void f5(void) throw() {
   try {
      f1();
   }
   catch(...) {
      handler();
    }
}

// invalid, doesn't handle the int exception thrown from f1()
// void f3(void) throw() {
//   f1();
// }

void __declspec(nothrow) f2(void) {
   try {
      f1();
   }
   catch(int) {
      handler();
    }
}

// only valid if compiled without /EHc
// /EHc means assume extern "C" functions don't throw exceptions
extern "C" void f4(void);
void f4(void) {
   f1();
}

int main() {
   f2();

   try {
      f4();
   }
   catch(...) {
      printf_s("Caught exception from f4\n");
   }
   f5();
}
About to throw 1
in handler
About to throw 1
Caught exception from f4
About to throw 1
in handler

Voir aussi

try, throw et catch instructions (C++)
Meilleures pratiques C++ modernes pour la gestion des exceptions et des erreurs