Je travaille sur un projet dans lequel nous restructurons l'ancien code C en un nouveau C++ où, pour la gestion des erreurs, nous utilisons des exceptions.
Nous créons différents types d'exceptions pour différents modules.
Je ne vois pas que cela en vaut la peine, mais je n’ai aucun argument valable pour prouver mon propos. Donc, si nous avions écrit la bibliothèque standard, vous devriez voir vector_exception, list_exception et ainsi de suite.
En y réfléchissant, je suis tombé sur cette question:
Quand devriez-vous créer votre propre type d'exception et quand devriez-vous vous en tenir aux exceptions déjà créées dans la bibliothèque std?
En outre, quels pourraient être les problèmes auxquels nous pourrions faire face dans un proche avenir si nous adoptions l'approche ci-dessus?
Créez vos propres types d'exceptions lorsque:
catch
. Ils doivent toujours avoir une base commune afin que vous puissiez avoir une manipulation commune lorsque cela est appropriéAvoir des exceptions séparées list
et vector
ne semble pas la peine, sauf si elles ont quelque chose de particulier qui ressemble à une liste ou à un vecteur. Est-ce que vous allez vraiment avoir une manipulation de capture différente selon le type de conteneur qui a eu une erreur?
Inversement, il peut être judicieux de définir des types d’exception distincts pour les éléments récupérables au moment de l’exécution, par rapport aux éléments annulés mais susceptibles d’être réessayés, par rapport aux éléments définitivement fatals ou indiquant un bogue.
Utiliser la même exception partout est facile. Surtout en essayant d'attraper cette exception. Malheureusement, cela ouvre la porte à gestion des exceptions Pokemon . Cela comporte le risque d'attraper des exceptions inattendues.
L'utilisation d'une exception dédiée pour tous les différents modules ajoute plusieurs avantages:
Je pense qu’en plus des raisons évoquées dans d’autres réponses, il pourrait s’agir de la lisibilité du code, car les programmeurs consacrent beaucoup de temps à le supporter. Considérons deux morceaux de code (en supposant qu’ils génèrent l’erreur "frame vide"):
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw std::exception("Error: empty frame");
}
}
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw EmptyFrame();
}
}
Dans le second cas, je pense qu'il est plus lisible et que le message destiné à l'utilisateur (imprimé avec la fonction what ()) est masqué dans cette classe d'exceptions personnalisée.
Je vois deux raisons possibles.
1) Si vous souhaitez stocker des informations personnalisées sur l'exception dans la classe d'exceptions, vous avez besoin d'une exception avec des membres de données supplémentaires. Vous hériterez souvent de l'une des classes d'exceptions std.
2) Si vous souhaitez différencier les exceptions. Par exemple, supposons que vous ayez deux invalid argument
_ situations et dans votre bloc catch
vous voulez pouvoir différencier les deux. Vous pouvez créer deux classes enfants de std::invalid_argument
Le problème que je vois avec chaque élément de la STL déclenchant une exception de mémoire distincte est que certaines parties de la stl sont construites sur d'autres, de sorte que la file d'attente doit capturer et traduire les exceptions de liste, ou que les clients de la file d'attente doivent savoir comment gérer les exceptions de liste. .
Je peux voir une certaine logique dans la séparation des exceptions de différents modules, mais je peux aussi voir la logique d'avoir une classe de base d'exception à l'échelle du projet. Je peux aussi voir la logique d'avoir des classes de type exception.
L’alliance impie serait de construire une hiérarchie d’exceptions:
std::exception
EXProjectBase
EXModuleBase
EXModule1
EXModule2
EXClassBase
EXClassMemory
EXClassBadArg
EXClassDisconnect
EXClassTooSoon
EXClassTooLate
Insistez ensuite sur le fait que chaque exception déclenchée est dérivée de À LA FOIS un module et une classification. Ensuite, vous pouvez capturer tout ce qui est logique pour le récupérateur, tel qu'une déconnexion, plutôt que d'avoir à capturer séparément HighLevelDisconnect et LowLevelDisconnect.
D'un autre côté, il est également tout à fait juste de suggérer que l'interface HighLevel devrait avoir complètement traité les défaillances LowLevel et qu'elles ne devraient jamais être vues par le client API HighLevel. C’est là que la capture par module serait une fonctionnalité utile de dernier recours.
Je demanderais, de la try...catch
section, "ce code sait-il comment récupérer de l'erreur, ou pas?"
Code utilisant try...catch
prévoit qu’une exception pourrait se produire. Raisons pour lesquelles vous écririez try...catch
du tout au lieu de laisser l’exception simplement passer sont les suivants:
Dans les cas 1 et 2, vous n'avez probablement pas besoin de vous inquiéter des types d'exceptions utilisateur, elles ressembleront à ceci:
try {
....
} catch (const std::exception & e) {
// print or tidy up or whatever
throw;
} catch (...) {
// print or tidy up or whatever
// also complain that std::exception should have been thrown
throw;
}
Ce sera probablement 99% des cas réels, selon mon expérience.
Parfois, vous voudrez récupérer l'erreur. Peut-être que la fonction parent sait qu’une bibliothèque échouera dans certaines conditions qui peut être corrigée de manière dynamique, ou il existe une stratégie permettant de réessayer un travail en utilisant différents mécanismes.
Parfois, le bloc catch
sera écrit spécifiquement parce que le code de niveau inférieur a été conçu pour lancer des exceptions dans le cadre de son flux normal. (Je le fais souvent lors de la lecture de données externes non fiables, où beaucoup de choses peuvent de manière prévisible se tromper.)
Dans ces cas, plus rares, les types définis par l'utilisateur ont du sens.