Gestion avancée des erreurs en C++ : de try/catch aux pratiques industrielles
La gestion des erreurs est l'une des pierres angulaires de la programmation industrielle. En C++, le développeur dispose non seulement du mécanisme classique try/catch, mais aussi de tout un arsenal de techniques avancées : du RAII aux garanties de sécurité des exceptions. Dans cet article, nous allons voir comment écrire du code C++ robuste qui gère correctement les défaillances, ne fuit pas les ressources et reste lisible.
1. Bases et pièges standards de try/catch
La syntaxe de base est connue de tous :
#include <iostream>#include <stdexcept>
void dangerousFunction(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } // ... travail avec value}
int main() { try { dangerousFunction(-5); } catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Generic error: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown exception caught" << std::endl; } return 0;}Points importants :
- Attrapez par référence —
catch (const std::exception& e)évite le découpage d'objets. - L'ordre des blocs catch est important : d'abord les exceptions les plus spécifiques, puis les générales.
- N'abusez pas de
catch (...)— il cache toutes les erreurs, y compris les critiques (par exemple,std::bad_alloc). - Ne lancez jamais d'exceptions depuis les destructeurs — cela entraîne un comportement indéfini (généralement une terminaison brutale).
2. RAII comme alternative à try/catch pour la gestion des ressources
RAII (Resource Acquisition Is Initialization) est le modèle clé du C++ qui rend les exceptions sûres. L'idée : la ressource est acquise dans le constructeur et libérée dans le destructeur. Si une exception se produit, les destructeurs de tous les objets créés sont appelés automatiquement (déroulement de la pile).
#include <fstream>#include <vector>#include <stdexcept>
void processFile(const std::string& filename) { std::ifstream file(filename); // RAII : le fichier s'ouvre dans le constructeur if (!file.is_open()) { throw std::runtime_error("Cannot open file: " + filename); }
std::vector<int> data; int value; while (file >> value) { data.push_back(value); } // Le destructeur de file fermera le fichier automatiquement, // même en cas d'exception (par exemple, bad_alloc lors de push_back)}Avantages du RAII par rapport au try/catch manuel :
- Pas besoin d'écrire de blocs
finally— les destructeurs garantissent le nettoyage. - Le code devient plus linéaire et lisible.
- Les exceptions n'entraînent pas de fuites de mémoire, de descripteurs de fichiers, etc.
3. Garanties de sécurité des exceptions
En C++, on distingue trois niveaux de garanties pour les fonctions :
- Garantie no-throw — la fonction ne lance jamais d'exception. Marquée
noexcept. - Garantie forte — si une exception se produit, l'état du programme reste inchangé (opération atomique).
- Garantie de base — en cas d'exception, il n'y a pas de fuite de ressources, les objets restent dans un état cohérent, mais leur valeur peut changer.
Exemple avec garantie forte (idiome copy-and-swap) :
#include <algorithm> // pour std::swap#include <vector>#include <stdexcept>
class SafeVector { std::vector<int> data_;public: void push_back(int value) { // 1. Crée