Partager via


Meilleures pratiques C++ modernes pour la gestion des exceptions et des erreurs

En C++ moderne, dans la plupart des cas, la meilleure façon de signaler et de traiter les erreurs logiques et les erreurs d’exécution est d’utiliser des exceptions. C’est particulièrement vrai lorsque la pile peut contenir plusieurs appels de fonction entre la fonction qui détecte l’erreur et la fonction qui a le contexte pour traiter l’erreur. Les exceptions constituent un moyen formel et bien défini pour le code qui détecte les erreurs de transmettre l’information à la pile d’appels.

Utiliser des exceptions pour du code exceptionnel

Les erreurs de programme sont souvent divisées en deux catégories :

  • Erreurs logiques provoquées par des erreurs de programmation. Par exemple, une erreur « index hors plage ».
  • Erreurs d’exécution qui dépassent le contrôle du programmeur. Par exemple, une erreur « service réseau indisponible ».

Dans la programmation de style C et dans COM, le signalement des erreurs est géré soit en renvoyant une valeur qui représente un code d’erreur ou un code d’état pour une fonction particulière, soit en définissant une variable globale que l’appelant peut éventuellement récupérer après chaque appel de fonction pour voir si des erreurs ont été signalées. Par exemple, la programmation COM utilise la valeur de retour HRESULT pour communiquer des erreurs à l’appelant. Et l’API Win32 a la fonction GetLastError pour récupérer la dernière erreur signalée par la pile des appels. Dans ces deux cas, il est à l’appelant de reconnaître le code et de lui répondre de manière appropriée. Si l’appelant ne gère pas explicitement le code d’erreur, le programme peut se bloquer sans avertissement. Ou bien, il peut continuer à s’exécuter à l’aide de données incorrectes et produire des résultats incorrects.

Les exceptions sont préférées en C++ moderne pour les raisons suivantes :

  • Une exception force l’appel du code pour reconnaître une condition d’erreur et la gérer. Les exceptions non prises en charge arrêtent l’exécution du programme.
  • Une exception passe au point de la pile des appels qui peut gérer l’erreur. Les fonctions intermédiaires peuvent permettre à l’exception de se propager. Ils n’ont pas besoin de coordonner avec d’autres couches.
  • Le mécanisme de déroulement de la pile d’exceptions détruit tous les objets concernés après le déclenchement d’une exception, selon des règles bien définies.
  • Une exception permet une séparation nette entre le code qui détecte l’erreur et le code qui la gère.

L’exemple simplifié suivant montre la syntaxe nécessaire pour lancer et attraper des exceptions en C++ :

#include <stdexcept>
#include <limits>
#include <iostream>

using namespace std;

void MyFunc(int c)
{
    if (c > numeric_limits< char> ::max())
    {
        throw invalid_argument("MyFunc argument too large.");
    }
    //...
}

int main()
{
    try
    {
        MyFunc(256); //cause an exception to throw
    }

    catch (invalid_argument& e)
    {
        cerr << e.what() << endl;
        return -1;
    }
    //...
    return 0;
}

Les exceptions en C++ ressemblent à celles des langages tels que C# et Java. Dans le bloc try, si une exception est levée, elle est rattrapée par le premier catchbloc associé dont le type correspond à celui de l’exception. En d’autres termes, l’exécution passe de l’instruction throw à l’instruction catch. Si aucun bloc catch utilisable n’est trouvé, std::terminate est appelé et le programme se ferme. En C++, tout type peut être levée; toutefois, nous vous recommandons de lever un type qui dérive directement ou indirectement de std::exception. Dans l’exemple précédent, le type d’exception, invalid_argument, est défini dans la bibliothèque standard dans le fichier d’en-tête <stdexcept>. C++ ne fournit pas ou ne nécessite pas de bloc de finally pour vous assurer que toutes les ressources sont libérées si une exception est levée. L’acquisition de ressources est l’idiome (RAII), qui utilise des pointeurs intelligents, fournit les fonctionnalités requises pour le nettoyage des ressources. Pour plus d’informations, voir Comment faire : Concevoir pour la sécurité des exceptions. Pour plus d’informations sur le mécanisme de déroulement de la pile C++, consultez exceptions et le déroulement de la pile.

Recommandations de base

La gestion robuste des erreurs est difficile dans n’importe quel langage de programmation. Bien que les exceptions offrent plusieurs fonctionnalités permettant une bonne gestion des erreurs, elles ne peuvent pas faire tout le travail à votre place. Pour profiter des avantages du mécanisme d’exception, gardez les exceptions à l’esprit lorsque vous concevez votre code.

  • Utilisez les assertions pour vérifier les conditions qui doivent toujours être vraies ou toujours fausses. Utilisez les exceptions pour vérifier les erreurs susceptibles de se produire, par exemple les erreurs de validation des données saisies dans les paramètres des fonctions publiques. Pour plus d’informations, voir la section Exceptions et assertions.
  • Utilisez des exceptions lorsque le code qui gère l’erreur est séparé du code qui détecte l’erreur par un ou plusieurs appels de fonction intermédiaires. Réfléchissez à la possibilité d’utiliser plutôt des codes d’erreur dans les boucles critiques en termes de niveau de performance, lorsque le code qui gère l’erreur est étroitement lié au code qui la détecte.
  • Pour chaque fonction susceptible de lancer ou de propager une exception, il convient de fournir l’une des trois garanties d’exception : la garantie forte, la garantie de base ou la garantie de non-renversement (noexcept). Pour plus d’informations, voir Comment faire : Concevoir pour la sécurité des exceptions.
  • Lancez des exceptions par valeur, rattrapez-les par référence. Ne interceptez pas ce que vous ne pouvez pas gérer.
  • N’utilisez pas les spécifications d’exception, qui sont obsolètes en C++11. Pour plus d’informations, voir les spécifications et la noexcept section sur les exceptions.
  • Utilisez des types d’exceptions de bibliothèque standard lorsqu’ils s’appliquent. Dérivez des types d’exceptions personnalisés de la hiérarchie exceptionclasse.
  • Ne permettez pas aux exceptions de s’échapper des destructeurs ou des fonctions de désallocation de la mémoire.

Exceptions et performances

Le mécanisme d’exception a un coût de niveau de performance minimal si aucune exception n’est levée. Si une exception est levée, le coût de la traversée et du déroulement de la pile est à peu près comparable au coût d’un appel de fonction. D’autres structures de données sont requises pour suivre la pile des appels une fois qu’un bloc try est entré, et d’autres instructions sont nécessaires pour décompresser la pile si une exception est levée. Toutefois, dans la plupart des cas, le coût en termes de niveau de performance et d’empreinte mémoire n’est pas significatif. L’effet négatif des exceptions sur le niveau de performance n’est susceptible d’être significatif que sur les systèmes à mémoire limitée. Ou encore, dans les boucles critiques en termes de niveau de performance, lorsqu’une erreur est susceptible de se produire régulièrement et qu’il existe un lien étroit entre le code qui la traite et celui qui la signale. En tout état de cause, il est impossible de connaître le coût réel des exceptions sans profilage et mesure. Même dans les rares cas où le coût est important, vous pouvez le mettre en balance avec l’amélioration de la correction, la facilité de maintenance et les autres avantages offerts par une politique d’exception bien conçue.

Exceptions contre assertions

Les exceptions et les alertes sont deux mécanismes distincts permettant de détecter les erreurs d’exécution d’un programme. Utilisez assert instructions pour tester les conditions pendant le développement qui doivent toujours être vraies ou toujours false si tout votre code est correct. Il est inutile de gérer une telle erreur en utilisant une exception, car l’erreur indique que quelque chose dans le code doit être corrigé. Il ne s’agit pas d’une condition dont le programme doit se remettre au moment de l’exécution. Une assert arrête l’exécution à l’instruction afin de pouvoir inspecter l’état du programme dans le débogueur. Une exception poursuit l’exécution à partir du premier gestionnaire de capture approprié. Utilisez les exceptions pour vérifier les conditions d’erreur susceptibles de se produire au moment de l’exécution, même si votre code est correct, par exemple « fichier introuvable » ou « manque de mémoire ». Les exceptions permettent de gérer ces conditions, même si la reprise se contente d’envoyer un message dans un journal et de mettre fin au programme. Vérifiez toujours les arguments des fonctions publiques en utilisant des exceptions. Même si votre fonction est exempte d’erreurs, il se peut que vous n’ayez pas un contrôle total sur les arguments qu’un utilisateur peut lui transmettre.

Exceptions C++ et exceptions Windows SEH

Les programmes C et C++ peuvent utiliser le mécanisme de gestion des exceptions structurées (SEH) du système d’exploitation Windows. Les concepts de SEH ressemblent à ceux des exceptions C++, sauf que SEH utilise les constructions __try, __exceptet __finally au lieu de try et catch. Dans le compilateur Microsoft C++ (MSVC), les exceptions C++ sont implémentées pour SEH. Toutefois, lorsque vous écrivez du code C++, utilisez la syntaxe d’exception C++.

Pour plus d’informations à propos de SEH, consultez Structured Exception Handling (C/C++).

Spécifications d’exception et noexcept

Les spécifications d’exception ont été introduites en C++ comme moyen de spécifier les exceptions qu’une fonction peut lancer. Cependant, les spécifications d’exception se sont avérées problématiques dans la pratique et sont dépréciées dans le projet de norme C++11. Nous vous recommandons de ne pas utiliser throw spécifications d’exception à l’exception de throw(), ce qui indique que la fonction n’autorise aucune exception à échapper. Si vous devez utiliser des spécifications d’exception du formulaire déconseillé throw( type-name ), la prise en charge de MSVC est limitée. Pour plus d’informations, voir Spécifications des exceptions (throw) . Le spécificateur noexcept est introduit en C++11 comme alternative préférée à throw().

Voir aussi

Comment : établir une interface entre le code exceptionnel et le code non exceptionnel
Informations de référence sur le langage C++
Bibliothèque C++ standard