Gestion des erreurs et des exceptions (Modern C++)
En C++ moderne, dans la plupart des scénarios, la meilleure méthode pour signaler et gérer des erreurs de logique et des 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 possède le contexte pour savoir la gérer. Les exceptions offrent un moyen formel et bien défini pour que le code qui détecte des erreurs transmettent les informations jusqu'en haut de la pile des appels.
Les erreurs de programme sont généralement réparties en deux catégories : les erreurs logiques, qui sont générées par des erreurs de programmation, par exemple une erreur de type « index hors limites » et les erreurs d'exécution qui sont hors de contrôle du programmeur, par exemple une erreur de type « service réseau non disponible ». En programmation de style C et en COM, le rapport d'erreurs est géré soit en retournant 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 extraire 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 qui a été signalée par la pile des appels. Dans les deux cas, il appartient à l'appelant d'identifier le code et d'y 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 continuer à s'exécuter avec des données erronées et générer des résultats incorrects.
Les exceptions sont préférées en C++ moderne pour les raisons suivantes :
Une exception force le code appelant à identifier une condition d'erreur et à la gérer. Les exceptions non gérées arrêtent l'exécution du programme.
Une exception accède au point de la pile des appels qui peut gérer l'erreur. Les fonctions intermédiaires peuvent permettre la propagation de l'exception. Elles ne doivent pas se coordonner avec d'autres couches.
Le mécanisme de déroulement de la pile d'exception détruit tous les objets de la portée en fonction de règles bien définies, une fois qu'une exception est levée.
Une exception active une séparation nette entre le code qui détecte l'erreur et le code qui gère l'erreur.
L'exemple simplifié suivant illustre la syntaxe nécessaire pour la levée et l'interception des exceptions en C++.
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
class MyClass
{
public:
void MyFunc(char 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 comme C# et Java. Dans le bloc try, si une exception est levée, elle est interceptée par le premier bloc catch 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, et ne requiert pas, de bloc finally pour vérifier que toutes les ressources sont libérées si une exception est levée. L'idiome RAII (resource acquisition is initialization), qui utilise des pointeurs intelligents, fournit les fonctionnalités requises pour le nettoyage de ressources. Pour plus d'informations, consultez Comment : conception pour la sécurité des exceptions. Pour plus d'informations sur le mécanisme de déroulement de pile C++, consultez Exceptions et déroulement de pile en C++.
Instructions de base
Une gestion des erreurs fiable est un vrai défi pour tous les langages de programmation. Bien que les exceptions fournissent plusieurs fonctionnalités qui prennent en charge la bonne gestion des erreurs, elles ne peuvent pas tout faire à votre place. Pour tirer profit des avantages du mécanisme d'exception, gardez les exceptions à l'esprit lorsque vous concevez votre code.
Utilisez les assertions pour vérifier les erreurs qui ne doivent jamais se produire. Utilisez les exceptions pour vérifier les erreurs susceptibles de se produire : par exemple, les erreurs dans la validation d'entrée sur les paramètres de fonctions publiques. Pour plus d'informations, consultez la section intitulée Comparaison des exceptions et des assertions.
Utilisez les exceptions lorsque le code qui gère l'erreur peut être séparé du code qui détecte l'erreur par un ou plusieurs appels de fonction intermédiaires. Déterminez si vous devez utiliser des codes d'erreur à la place dans les boucles aux performances critiques lorsque le code qui gère l'erreur est fortement couplé au code qui la détecte. Pour plus d'informations sur les circonstances dans lesquelles ne pas utiliser les exceptions, consultez When Not to Use Exceptions.
Pour chaque fonction qui peut lever ou propager une exception, fournissez une des trois garanties d'exception suivantes : la garantie forte, la garantie de base ou la garantie nothrow (noexcept). Pour plus d'informations, consultez Comment : conception pour la sécurité des exceptions.
Lever les exceptions par valeur, les intercepter par référence. N'interceptez pas ce que vous ne pouvez pas gérer. Pour plus d'informations, consultez Règles sur la levée et l'interception d'exceptions (C++).
N'utilisez pas les spécifications d'exceptions, qui sont déconseillées dans C++11. Pour plus d'informations, consultez la section intitulée Spécifications d'exception et noexcept.
Utilisez les types d'exceptions standard des bibliothèques lorsqu'ils s'appliquent. Dérivez les types d'exceptions personnalisées de la hiérarchie Classe Exception. Pour plus d'informations, consultez Comment : Utilisez des objets exception standard de bibliothèque.
N'autorisez pas les exceptions à échapper aux destructeurs ou aux fonctions de désallocation de mémoire.
Exceptions et performances
Le mécanisme d'exception a un impact minimal sur les performances, si aucune exception n'est levée. Si une exception est levée, le coût de la traversée de la pile et du déroulement est approximativement comparable au coût d'un appel de fonction. Des structures d'informations supplémentaires sont requises pour suivre la pile des appels après l'entrée dans un bloc try, et des instructions supplémentaire sont requise pour dérouler la pile si une exception est levée. Toutefois, dans la plupart des scénarios, le coût de performances et l'encombrement mémoire ne sont pas significatifs. L'effet négatif des exceptions sur les performances peut être significatif uniquement sur les systèmes très limités en mémoire, ou dans les boucles aux performances critiques où une erreur est susceptible de se produire régulièrement et où le code de gestion correspondant est fortement couplé au code de signalisation de l'erreur. Dans tous les cas, il est impossible de connaître le coût réel des exceptions sans profilage et mesurage. Même dans les rares cas où le coût est significatif, vous pouvez le mesurer à une exactitude améliorée, une facilité de maintenance accrue et d'autres avantages fournis par une stratégie d'exception bien conçue.
Comparaison des exceptions et des assertions
Les exceptions et les assertions sont deux mécanismes distincts qui servent à détecter des erreurs d'exécution dans un programme. Utilisez les assertions à utiliser pour tester les conditions pendant le développement qui ne doivent jamais être vraies si tout votre code est correct. Il n'existe aucun problème à gérer une telle erreur en utilisant une exception, parce que l'erreur indique que quelque chose dans le code doit être corrigé et qu'elle ne correspond pas à un état à partir duquel le programme doit récupérer au moment de l'exécution. Une assertion interrompt l'exécution au niveau de l'instruction pour vous permettre d'inspecter l'état du programme dans le débogueur. Une exception poursuit l'exécution à partir du premier gestionnaire catch approprié. Utilisez les exceptions pour vérifier les conditions d'erreur qui peuvent se produire au moment de l'exécution même si votre code est correct : par exemple, « Fichier introuvable » ou « Mémoire insuffisante ». Vous pouvez récupérer de ces conditions, même si la récupération affiche simplement un message dans un journal et arrête le programme. Vérifiez toujours les arguments des fonctions publiques en utilisant des exceptions. Même si la fonction est exempte d'erreurs, vous pouvez ne pas avoir un contrôle complet sur les arguments qu'un utilisateur peut passer à celle-ci.
Comparaison des exceptions C++ et des exceptions SEH du système d'exploitation Windows
Les programmes C et C++ peuvent utiliser le mécanisme de gestion structurée des exceptions (SEH) du système d'exploitation Windows. Les concepts de gestion structurée des exceptions (SEH) ressemblent à ceux des exceptions C++, sauf que la gestion SEH utilise les constructions __try, __except et __finally au lieu de try et catch. Dans Visual C++, 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 concernant SEH, consultez Gestion structurée des exceptions (C/C++).
Spécifications d'exception et noexcept
Les spécifications d'exception ont été introduites dans C++ afin de spécifier les exceptions qu'une fonction peut lever. Toutefois, les spécifications d'exceptions se sont révélées problématiques dans la pratique et sont déconseillées dans le projet de norme C++11. Nous vous recommandons de ne pas utiliser les spécifications d'exception, sauf throw() qui indique que l'exception ne permet pas d'exceptions à l'échappement. Si vous devez utiliser des spécifications d'exception de type throw(type), sachez que Visual C++ s'éloigne de la norme de certaines façons. Pour plus d'informations, consultez Spécifications d'exception. Le spécificateur noexcept est introduit dans C++11 en tant que solution de remplacement privilégiée de throw().
Voir aussi
Concepts
Comment : établir une interface entre le code exceptionnel et le code non exceptionnel