En C++, le traitement des exceptions s'accompagne d'un mécanisme de déroulement de pile — destruction automatique (destructuration) des objets locaux dans la pile d'appels, créés avant l'apparition de l'exception. Ce phénomène est important pour la libération correcte des ressources.
À l'origine, lors de la conception de C++, il manque un collecteur de déchets intégré, et la tâche de libération des ressources (mémoire, fichiers, sockets) incombe au programmeur. Le traitement des exceptions devait garantir une libération correcte lors du retour du code à cause d'une erreur.
Sans mécanisme de destruction automatique des objets lors d'une exception, il y a des fuites de ressources. Si les ressources ne sont pas libérées manuellement dans chaque bloc catch, le code devient complexe et peu fiable.
C++ implémente le déroulement de pile, lorsque lors du lancement d'une exception, tous les objets situés dans la pile dans la portée avant le throw sont détruits dans l'ordre inverse de leur création. Leur destructeur est appelé automatiquement.
La méthode classique pour libérer constamment des ressources est d'appliquer le motif RAII (Resource Acquisition Is Initialization) : toutes les ressources sont "enveloppées" dans des objets qui libèrent la ressource qu'ils possèdent lors de leur destruction.
Exemple de code:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("Impossible d'ouvrir le fichier"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // Revient à l'état précédent s'il y a throw // ... travail avec le fichier throw std::runtime_error("Une erreur s'est produite"); // fclose sera appelé automatiquement }
Caractéristiques clés:
Pourquoi ne pas simplement utiliser try-catch et appeler manuellement delete ou fclose dans le bloc catch pour éviter les fuites ?
Réponse:
C'est peu pratique et peu fiable : il est facile d'oublier de fermer une ressource, surtout en cas de plusieurs points de sortie ou de ressources imbriquées. Les destructeurs sont appelés même si une exception "traverse" une fonction, catch n'est donc pas nécessaire.
Les objets sur le tas (heap) seront-ils détruits s'ils ne sont pas "enveloppés" dans des objets sur la pile lors du déroulement de pile ?
Réponse:
Non. Le déroulement de pile appelle les destructeurs uniquement pour les objets situés dans la pile. Pour que les objets sur le tas soient détruits correctement, ils doivent être possédés par un objet sur la pile (par exemple, via des pointeurs intelligents).
Que se passe-t-il si un destructeur lance une exception lors du déroulement de pile ?
Réponse:
Si une deuxième exception est levée pendant le déroulement de la pile lors de la destruction d'un objet, le programme se terminera en appelant std::terminate(). Ne levez jamais d'exceptions à partir de destructeurs !
Le développeur ferme manuellement les fichiers et libère la mémoire via des blocs catch sans utiliser RAII.
Avantages :
Inconvénients :
Les fichiers et ressources sont enveloppés dans des classes d'encapsulation RAII. Résultat : la libération se produit dans les destructeurs, indépendamment de throw/catch.
Avantages :
Inconvénients :