Dois-je hériter de std :: exception?

98

J'ai vu au moins une source fiable (une classe C ++ que j'ai prise) recommander que les classes d'exceptions spécifiques à l'application en C ++ devraient hériter de std::exception. Je ne suis pas sûr des avantages de cette approche.

En C #, les raisons d'hériter de ApplicationExceptionsont claires: vous obtenez une poignée de méthodes, de propriétés et de constructeurs utiles et vous n'avez qu'à ajouter ou remplacer ce dont vous avez besoin. Avec std::exceptionil semble que tout ce que vous obtenez est une what()méthode de remplacement, que vous pouvez tout aussi bien créer vous-même.

Alors, quels sont les avantages, le cas échéant, de l'utilisation std::exceptioncomme classe de base pour ma classe d'exceptions spécifique à l'application? Y a-t-il de bonnes raisons de ne pas hériter std::exception?

John M Gant
la source
Vous voudrez peut-être jeter un oeil à ceci: stackoverflow.com/questions/1605778/1605852#1605852
sbi
2
Bien que, comme une note secondaire sans rapport avec la question particulière, les classes C ++ que vous prenez n'ont pas nécessairement besoin d'être des sources fiables sur les bonnes pratiques tout simplement hors de leur propre droit.
Christian Rau

Réponses:

69

Le principal avantage est que le code utilisant vos classes n'a pas besoin de connaître le type exact de ce que vous faites throw, mais peut simplement catchle std::exception.

Edit: comme Martin et d'autres l'ont noté, vous voulez en fait dériver de l'une des sous-classes std::exceptiondéclarées dans l'en- <stdexcept>tête.

Nikolai Fetissov
la source
21
Il n'y a aucun moyen de transmettre un message à std :: exception. std :: runtime_error accepte une chaîne et est dérivé de std :: exception.
Martin York
14
Vous ne devriez pas passer un message au constructeur de type d'exception (considérez que les messages doivent être localisés.) Au lieu de cela, définissez un type d'exception qui catégorise l'erreur sémantiquement, stockez dans l'objet d'exception ce dont vous avez besoin pour formater un message convivial , puis faites-le sur le site de capture.
Emil
std::exceptionest défini dans l'en- <exception>tête ( preuve ). -1
Alexander Shukaev
5
Alors, quel est votre point? La définition implique la déclaration, qu'est-ce qui ne va pas ici?
Nikolai Fetissov
40

Le problème avec std::exceptionest qu'il n'y a pas de constructeur (dans les versions conformes à la norme) qui accepte un message.

En conséquence, je préfère dériver de std::runtime_error. Ceci est dérivé de std::exceptionmais ses constructeurs vous permettent de passer une C-String ou un std::stringau constructeur qui sera retourné (en tant que a char const*) lors de l' what()appel.

Martin York
la source
11
Ce n'est pas une bonne idée de formater un message convivial au moment du lancer car cela associerait du code de niveau inférieur à une fonctionnalité de localisation et ainsi de suite. Au lieu de cela, stockez dans l'objet d'exception toutes les informations pertinentes et laissez le site catch formater un message convivial basé sur le type d'exception et les données qu'il transporte.
Emil
16
@ Emil: Bien sûr, si vous faites exception, portez des messages affichables par l'utilisateur, bien que généralement ils soient uniquement à des fins de journalisation. Sur le site de projection, vous n'avez pas le contexte pour créer un message utilisateur (car cela peut être une bibliothèque de toute façon). Le site catch aura plus de contexte et pourra générer le msg approprié.
Martin York le
3
Je ne sais pas d'où vous avez l'idée que les exceptions sont uniquement à des fins de journalisation. :)
Emil
1
@Emil: Nous avons déjà traversé cet argument. Là où j'ai souligné que ce n'est pas ce que vous mettez dans l'exception. Votre compréhension du sujet semble bonne mais votre argumentation est imparfaite. Les messages d'erreur générés pour l'utilisateur sont complètement différents des messages de journal pour le développeur.
Martin York
1
@Emil. Cette discussion date d'un an. Créez votre propre question à ce stade. Vos arguments en ce qui me concerne sont tout simplement faux (et sur la base des votes, les gens ont tendance à être d'accord avec moi). Voir Qu'est-ce qu'un «bon nombre» d'exceptions à implémenter pour ma bibliothèque? et Attraper des exceptions générales est-il vraiment une mauvaise chose? Vous n'avez fourni aucune nouvelle information depuis votre précédente diatribe.
Martin York
17

La raison de l'héritage de std::exceptioncette classe de base "standard" pour les exceptions, il est donc naturel pour les autres membres d'une équipe, par exemple, de s'attendre à cela et d'attraper la base std::exception.

Si vous recherchez la commodité, vous pouvez hériter de std::runtime_errorce std::stringconstructeur fournit .

Aleksei Potov
la source
2
Dériver de tout autre type d'exception standard à l'exception de std :: exception n'est probablement pas une bonne idée. Le problème est qu'ils dérivent de std :: exception de manière non virtuelle, ce qui peut conduire à des bogues subtils impliquant l'héritage multiple où un catch (std :: exception &) pourrait échouer silencieusement à attraper une exception en raison de la conversion en std :: exception étant ambigu.
Emil
2
J'ai lu l'article sur le boost sur le sujet. Cela semble raisonnable dans un sens purement logique. Mais je n'ai jamais vu MH dans les exceptions dans la nature. Je n'envisagerais pas non plus d'utiliser MH dans les exceptions, il semble donc être un marteau pour résoudre un problème inexistant. Si c'était un vrai problème, je suis sûr que le comité des normes aurait pris des mesures pour corriger une lacune aussi évidente. Donc, mon avis est que std :: runtime_error (et sa famille) sont toujours des exceptions parfaitement acceptables à lancer ou à dériver.
Martin York le
5
@Emil: Dériver d'autres exceptions standard std::exceptionest d' ailleurs une excellente idée. Ce que vous ne devriez pas faire, c'est hériter de plus d'un.
Mooing Duck
@MooingDuck: Si vous dérivez de plus d'un type d'exception standard, vous vous retrouverez avec std :: exception dérivée (non virtuellement) plusieurs fois. Voir boost.org/doc/libs/release/libs/exception/doc/… .
Emil
1
@Emil: Cela découle de ce que j'ai dit.
Mooing Duck
13

Une fois, j'ai participé au nettoyage d'une grande base de code où les auteurs précédents avaient jeté des ints, HRESULTS, std :: string, char *, des classes aléatoires ... des trucs différents partout; nommez simplement un type et il a probablement été jeté quelque part. Et pas du tout de classe de base commune. Croyez-moi, les choses étaient beaucoup plus ordonnées une fois que nous sommes arrivés au point que tous les types lancés avaient une base commune que nous pouvions attraper et savoir que rien n'allait passer. Alors faites-vous plaisir (et à ceux qui devront maintenir votre code à l'avenir) une faveur et faites-le de cette façon dès le début.

timday
la source
12

Vous devez hériter de boost :: exception . Il fournit beaucoup plus de fonctionnalités et des moyens bien compris de transporter des données supplémentaires ... bien sûr, si vous n'utilisez pas Boost , ignorez cette suggestion.

James Schek
la source
5
Cependant, notez que boost :: exception lui-même ne dérive pas de std :: exception. Même lorsque vous dérivez de boost :: exception, vous devez également dériver de std :: exception (comme une note distincte, chaque fois que vous dérivez de types d'exception, vous devez utiliser l'héritage virtuel.)
Emil
10

Oui, vous devriez dériver std::exception.

D'autres ont répondu que std::exceptionle problème était que vous ne pouvez pas lui transmettre un message texte, mais ce n'est généralement pas une bonne idée d'essayer de formater un message utilisateur au moment du lancer. À la place, utilisez l'objet d'exception pour transporter toutes les informations pertinentes vers le site de capture qui peut ensuite formater un message convivial.

Emil
la source
6
+1, bon point. À la fois du point de vue de la séparation des préoccupations et de la perspective i18n, il est certainement préférable de laisser la couche de présentation construire le message de l'utilisateur.
John M Gant
@JohnMGant et Emil: Intéressant, pouvez-vous donner un exemple concret sur la façon dont cela peut être fait. Je comprends que l'on peut dériver std::exceptionet porter les informations de l'exception. Mais qui sera responsable de la création / du formatage du message d'erreur? La ::what()fonction ou autre chose?
alfC
1
Vous formateriez le message sur le site de capture, probablement au niveau de l'application (plutôt que de la bibliothèque). Vous l'attrapez par type, puis recherchez des informations, puis localisez / formatez le message utilisateur approprié.
Emil
9

La raison pour laquelle vous voudrez peut-être hériter de std::exceptionest parce que cela vous permet de lever une exception qui est interceptée selon cette classe, c'est-à-dire:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}
SingleNegationElimination
la source
6

Il y a un problème avec l'héritage que vous devriez connaître est le découpage d'objets. Lorsque vous écrivez throw e;une expression throw initialise un objet temporaire, appelé objet d'exception, dont le type est déterminé en supprimant tous les qualificatifs cv de niveau supérieur du type statique de l'opérande de throw. Ce n'est peut-être pas ce à quoi vous vous attendez. Exemple de problème que vous pourriez trouver ici .

Ce n'est pas un argument contre l'héritage, c'est juste une information «à connaître».

Kirill V. Lyadvinsky
la source
3
Je pense que la chose à emporter est que «jeter e»; est le mal, et «jeter»; est ok.
James Schek
2
Oui, throw;ça va, mais il n'est pas évident que vous deviez écrire quelque chose comme ça.
Kirill V. Lyadvinsky
1
C'est particulièrement pénible pour les développeurs Java où la relance se fait en utilisant "throw e;"
James Schek
Envisagez de dériver uniquement des types de base abstraits. Attraper un type de base abstrait par valeur vous donnera une erreur (en évitant le découpage); attraper un type de base abstrait par référence puis essayer d'en lancer une copie vous donnera une erreur (encore une fois en évitant le découpage.)
Emil
Vous pouvez également résoudre ce problème en ajoutant une fonction de membre, raise()en tant que tel: virtual void raise() { throw *this; }. N'oubliez pas de le remplacer dans chaque exception dérivée.
Justin Time - Réintègre Monica le
5

Différence: std :: runtime_error vs std :: exception ()

Que vous deviez en hériter ou non, cela dépend de vous. Standard std::exceptionet ses descendants standard proposent une structure hiérarchique d'exception possible (division en sous- logic_errorhiérarchie et sous- runtime_errorhiérarchie) et une interface d'objet d'exception possible. Si vous l'aimez, utilisez-le. Si pour une raison quelconque vous avez besoin de quelque chose de différent, définissez votre propre cadre d'exceptions.

Fourmi
la source
3

Puisque le langage lance déjà std :: exception, vous devez quand même l'attraper pour fournir un rapport d'erreur correct. Vous pouvez également utiliser la même capture pour toutes les exceptions inattendues de votre choix. De plus, presque toutes les bibliothèques qui lèvent des exceptions les dériveraient de std :: exception.

En d'autres termes, c'est soit

catch (...) {cout << "Unknown exception"; }

ou

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

Et la deuxième option est certainement meilleure.


la source
3

Si toutes vos exceptions possibles dérivent std::exception, votre bloc catch peut simplement catch(std::exception & e)et être assuré de tout capturer.

Une fois que vous avez capturé l'exception, vous pouvez utiliser cette whatméthode pour obtenir plus d'informations. C ++ ne prend pas en charge le typage canard, donc une autre classe avec une whatméthode nécessiterait une capture et un code différents pour l'utiliser.

Mark Ransom
la source
7
ne sous-classez pas std :: exception puis catch (std :: exception e). Vous devez attraper une référence à std :: exception & (ou un pointeur) ou bien vous découpez les données de la sous-classe.
jmucchiello
1
C'était une erreur stupide - je sais certainement mieux. stackoverflow.com/questions/1095225/…
Mark Ransom
3

La première question est de savoir s'il faut dériver d'un type d'exception standard ou non. Cela active un seul gestionnaire d'exceptions pour toutes les exceptions de bibliothèque standard et les vôtres, mais cela encourage également de tels gestionnaires fourre-tout. Le problème est qu'il ne faut attraper que les exceptions que l'on sait gérer. Dans main (), par exemple, intercepter toutes les exceptions std :: est probablement une bonne chose si la chaîne what () sera enregistrée en dernier recours avant de quitter. Ailleurs, cependant, il est peu probable que ce soit une bonne idée.

Une fois que vous avez décidé de dériver ou non d'un type d'exception standard, la question est de savoir qui devrait être la base. Si votre application n'a pas besoin d'i18n, vous pourriez penser que le formatage d'un message sur le site d'appel est tout aussi bon que d'enregistrer des informations et de générer le message sur le site d'appel. Le problème est que le message formaté n'est peut-être pas nécessaire. Mieux vaut utiliser un schéma de génération de message paresseux - peut-être avec une mémoire préallouée. Ensuite, si le message est nécessaire, il sera généré lors de l'accès (et éventuellement mis en cache dans l'objet d'exception). Ainsi, si le message est généré lorsqu'il est lancé, alors un dérivé std :: exception, comme std :: runtime_error, est nécessaire comme classe de base. Si le message est généré paresseusement, alors std :: exception est la base appropriée.

Rob
la source
0

Une autre raison de sous-classes d'exceptions est un meilleur aspect de conception lorsque vous travaillez sur de grands systèmes encapsulés. Vous pouvez le réutiliser pour des choses telles que les messages de validation, les requêtes des utilisateurs, les erreurs fatales du contrôleur, etc. Plutôt que de réécrire ou de relancer toutes vos validations comme des messages, vous pouvez simplement les «attraper» sur le fichier source principal, mais lancer l'erreur n'importe où dans votre ensemble complet de classes.

Par exemple, une exception fatale mettra fin au programme, une erreur de validation effacera uniquement la pile et une requête de l'utilisateur posera une question à l'utilisateur final.

Cela signifie également que vous pouvez réutiliser les mêmes classes mais sur des interfaces différentes. Par exemple, une application Windows peut utiliser une boîte de message, un service Web affichera le code HTML et le système de rapport l'enregistrera et ainsi de suite.

James Burnby
la source
0

Bien que cette question soit plutôt ancienne et ait déjà reçu de nombreuses réponses, je veux juste ajouter une note sur la façon de gérer correctement les exceptions en C ++ 11, car cela me manque continuellement dans les discussions sur les exceptions:

Utiliser std::nested_exceptionetstd::throw_with_nested

Il est décrit sur StackOverflow ici et ici , comment vous pouvez obtenir une trace de vos exceptions dans votre code sans avoir besoin d'un débogueur ou d'une journalisation encombrante, en écrivant simplement un gestionnaire d'exceptions approprié qui renverra les exceptions imbriquées.

Comme vous pouvez le faire avec n'importe quelle classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à une telle trace! Vous pouvez également jeter un œil à mon MWE sur GitHub , où une trace de retour ressemblerait à ceci:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Vous n'avez même pas besoin de sous-classe std::runtime_errorpour obtenir beaucoup d'informations lorsqu'une exception est levée.

Le seul avantage que je vois dans le sous-classement (au lieu de simplement utiliser std::runtime_error) est que votre gestionnaire d'exceptions peut intercepter votre exception personnalisée et faire quelque chose de spécial. Par exemple:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
GPMueller
la source