Doit-on dériver / hériter de std :: exception?

15

En concevant ma première bibliothèque C ++ «sérieuse», je me pose la question:

Est-ce un bon style de dériver ses exceptions std::exceptionet ses descendants?!

Même après avoir lu

Je ne suis toujours pas sûr. Parce que, en plus des pratiques courantes (mais peut-être pas bonnes), je suppose, en tant qu'utilisateur de bibliothèque, qu'une fonction de bibliothèque ne lancerait std::exceptions que lorsque les fonctions de bibliothèque standard échouent dans l'implémentation de la bibliothèque, et elle ne peut rien y faire. Mais quand même, lors de l'écriture du code d'application, pour moi, c'est très pratique, et aussi à mon humble avis, il suffit de jeter un std::runtime_error. Mes utilisateurs peuvent également compter sur l'interface minimale définie, comme what()ou sur des codes.

Et par exemple, mon utilisateur fournit des arguments défectueux, quoi de plus pratique que de lancer un std::invalid_argument, n'est-ce pas? Donc, combiné avec l'utilisation encore courante de std :: exception, je vois dans le code d'autres: Pourquoi ne pas aller plus loin et dériver de votre classe d'exception personnalisée (par exemple lib_foo_exception) et aussi de std::exception.

Pensées?

Superlokkus
la source
Je ne suis pas sûr de suivre. Ce std::exceptionn'est pas parce que vous héritez de que vous lancez un std::exception. En outre, std::runtime_errorhérite std::exceptionen premier lieu, et la what()méthode vient std::exception, non std::runtime_error. Et vous devez absolument créer vos propres classes d'exceptions au lieu de lancer des exceptions génériques telles que std::runtime_error.
Vincent Savard
3
La différence est que lorsque ma lib_foo_exceptionclasse dérive std::exception, l'utilisateur de la bibliothèque attraperait lib_foo_exceptionsimplement en attrapant std::exception, en plus de ne capturer que la bibliothèque. Je pourrais donc aussi demander à ma classe racine d'exception de bibliothèque d'hériter de std :: exception .
Superlokkus
3
@LightnessRacesinOrbit, je veux dire "... en plus de", comme "Combien de façons de capturer lib_foo_exception?" Avec l'héritage de std::exceptionvous pouvez le faire par catch(std::exception)OU par catch(lib_foo_exception). Sans dériver de std::exception, vous l'attraperiez si et seulement si , par catch(lib_foo_exception).
Superlokkus
2
@Superlokkus: Nous ignorons en quelque sorte catch(...). C'est là parce que le langage permet le cas que vous envisagez (et pour les bibliothèques "mal se comporter"), mais ce n'est pas la meilleure pratique moderne.
Courses de légèreté avec Monica
1
Une grande partie de la conception de la gestion des exceptions en C ++ a tendance à encourager les catchsites plus grossiers et plus généraux , ainsi que les transactions plus grossières qui modélisent une opération utilisateur. Si vous le comparez à des langages qui ne promeuvent pas l'idée d'une capture généralisée std::exception&, par exemple, ils ont souvent beaucoup plus de code avec des try/catchblocs intermédiaires concernés par des erreurs très spécifiques, ce qui diminue quelque peu la généralité du traitement des exceptions car il commence à se placer un accent beaucoup plus marqué sur la gestion manuelle des erreurs, ainsi que sur toutes les erreurs disparates qui pourraient éventuellement se produire.

Réponses:

29

Toutes les exceptions doivent hériter de std::exception.

Supposons, par exemple, que j'aie besoin d'appeler ComplexOperationThatCouldFailABunchOfWays()et que je souhaite gérer toutes les exceptions qu'il pourrait déclencher. Si tout hérite std::exception, c'est facile. Je n'ai besoin que d'un seul catchbloc, et j'ai une interface standard ( what()) pour obtenir des détails.

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

Si les exceptions n'héritent PAS de std::exception, cela devient beaucoup plus laid:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

En ce qui concerne l'opportunité de lancer runtime_errorou de invalid_argumentcréer vos propres std::exceptionsous-classes à lancer: Ma règle de base est d'introduire une nouvelle sous-classe chaque fois que j'ai besoin de traiter un type d'erreur particulier différemment des autres erreurs (c'est-à-dire chaque fois que j'ai besoin d'un catchbloc séparé ).

  • Si j'introduis une nouvelle sous-classe d'exception pour chaque type d'erreur concevable, même si je n'ai pas besoin de les gérer séparément, cela ajoute beaucoup de prolifération de classe.
  • Si je réutiliser les sous - classes existantes à moyenne quelque chose de spécifique ( par exemple, si un runtime_errorjeté ici signifie quelque chose de différent d'une erreur d'exécution générique), alors je risque d'entrer en conflit avec d' autres utilisations de la sous - classe existante.
  • Si je n'ai pas besoin de gérer une erreur spécifiquement, et si l'erreur que je lance correspond exactement à l'une des erreurs de la bibliothèque standard existante (comme invalid_argument), alors je réutilise la classe existante. Je ne vois tout simplement pas beaucoup d'avantages à ajouter une nouvelle classe dans ce cas. (Les directives de base C ++ ne sont pas d'accord avec moi ici - elles recommandent toujours d'utiliser vos propres classes.)

Les directives de base C ++ contiennent d'autres discussions et exemples.

Josh Kelley
la source
Toutes ces esperluettes! C ++ est bizarre.
SuperJedi224
2
@ SuperJedi224 et toutes ces lettres différentes! L'anglais est bizarre.
johannes
Cette justification n'a pas de sens pour moi. N'est-ce pas à cela catch (...)(avec les points de suspension littéraux)?
Maxpm
1
@Maxpm catch (...)est utile uniquement si vous n'avez pas besoin de faire quoi que ce soit avec ce qui est jeté. Si vous voulez faire quelque chose - par exemple, afficher ou enregistrer le message d'erreur spécifique, comme dans mon exemple - alors vous devez savoir ce que c'est.
Josh Kelley
9

Je suppose, en tant qu'utilisateur de bibliothèque, qu'une fonction de bibliothèque ne lancerait std :: exceptions que lorsque les fonctions de bibliothèque standard ont échoué dans l'implémentation de la bibliothèque, et qu'elle ne peut rien y faire

C'est une hypothèse incorrecte.

Les types d'exceptions standard sont fournis pour une utilisation "plus courante". Ils ne sont pas conçus pour être utilisés uniquement par la bibliothèque standard.

Oui, faites tout hériter de tout std::exception. Souvent, cela impliquera l'héritage de std::runtime_errorou std::logic_error. Tout ce qui est approprié pour la classe d'exception que vous implémentez.

Ceci est bien sûr subjectif - plusieurs bibliothèques populaires ignorent complètement les types d'exceptions standard, vraisemblablement pour dissocier les bibliothèques de la bibliothèque standard. Personnellement, je pense que c'est extrêmement égoïste! Cela rend la capture d'exceptions beaucoup plus difficile à corriger.

En parlant personnellement, je lance souvent un std::runtime_erroret j'en ai fini avec. Mais c'est entrer dans une discussion sur la granularité de vos classes d'exception, ce qui n'est pas ce que vous demandez.

Courses de légèreté avec Monica
la source