J'ai vu au moins une source fiable (une classe C++ que j'ai prise) recommande que les classes d'exception spécifiques à une application en C++ héritent de std::exception
. Je ne comprends pas bien les avantages de cette approche.
En C #, les raisons pour hériter de ApplicationException
sont claires: vous obtenez une poignée de méthodes, propriétés et constructeurs utiles et vous devez simplement ajouter ou remplacer ce dont vous avez besoin. Avec std::exception
, il semble que tout ce que vous obtenez est une méthode what()
à remplacer, que vous pourriez aussi bien créer vous-même.
Alors, quels sont les avantages, le cas échéant, d'utiliser std::exception
en tant que classe de base pour ma classe d'exception spécifique à une application? Existe-t-il de bonnes raisons de ne pas hériter de std::exception
?
Le principal avantage est que le code utilisant vos classes n'a pas à connaître le type exact de ce que vous throw
, mais peut simplement catch
le std::exception
.
Modifier: comme Martin et d'autres l'ont noté, vous souhaitez en fait dériver de l'une des sous-classes de std::exception
déclarées dans l'en-tête <stdexcept>
.
Le problème avec std::exception
est qu’il n’existe aucun constructeur (dans les versions compatibles standard) qui accepte un message.
En conséquence, je préfère dériver de std::runtime_error
. Ceci est dérivé de std::exception
mais ses constructeurs vous permettent de transmettre une chaîne C ou un std::string
au constructeur qui sera renvoyé (en tant que char const*
) lorsque what()
sera appelé.
La raison pour hériter de std::exception
est la classe de base "standard" pour les exceptions. Il est donc naturel que les autres membres d'une équipe, par exemple, s'attendent à cela et récupèrent la base std::exception
.
Si vous recherchez une solution pratique, vous pouvez hériter de std::runtime_error
qui fournit le constructeur std::string
.
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; il suffit de nommer un type et il a probablement été jeté quelque part. Et pas de classe de base commune du tout. Croyez-moi, les choses étaient beaucoup plus claires une fois que nous en sommes arrivés au point que tous les types lancés avaient une base commune que nous pouvions attraper et que nous savions que rien ne serait passé. Alors, s'il vous plait, faites-vous une faveur (et ceux qui devront maintenir votre code à l'avenir) et faites-le ainsi dès le début.
Vous devriez hériter de boost :: exception . Il fournit beaucoup plus de fonctionnalités et de moyens bien compris pour transporter des données supplémentaires ... bien sûr, si vous n'utilisez pas Boost , alors ignorez cette suggestion.
Oui, vous devriez dériver de std::exception
.
D'autres ont répondu que std::exception
posait le problème suivant: vous ne pouvez pas lui envoyer de message texte. Cependant, il n'est généralement pas conseillé de formater un message utilisateur au moment du lancement. Au lieu de cela, utilisez l'objet exception pour transporter toutes les informations pertinentes vers le site de capture, qui peut ensuite formater un message convivial.
La raison pour laquelle vous voudrez peut-être hériter de std::exception
est qu'elle vous permet de lever une exception interceptée en fonction de cette classe, c'est-à-dire:
class myException : public std::exception { ... };
try {
...
throw myException();
}
catch (std::exception &theException) {
...
}
Il y a un problème d'héritage que vous devriez connaître: le découpage en objet. Lorsque vous écrivez throw e;
, une expression-projection initialise un objet temporaire, appelé objet exception, dont le type est déterminé en supprimant tout qualificatif cv de niveau supérieur du type statique de l'opérande throw
. Cela pourrait ne pas être ce que 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 «doit savoir».
Différence: std :: runtime_error vs std :: exception ()
Que vous deviez en hériter ou pas, cela dépend de vous. La norme std::exception
et ses descendants standard proposent une structure hiérarchique d'exceptions possibles (division en sous-hiérarchie logic_error
et une sous-hiérarchie runtime_error
) 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’exception.
La première question est de savoir s'il faut dériver de n'importe quel 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 encourage également de tels gestionnaires interceptés. Le problème est que l'on ne doit intercepter que des exceptions que l'on sait gérer. Dans main (), par exemple, intercepter toutes les propriétés std :: exceptions est probablement une bonne chose si la chaîne what () sera consigné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 d'un type d'exception standard ou non, la question est de savoir quelle devrait être la base. Si votre application n'a pas besoin d'i18n, vous pouvez penser que le formatage d'un message sur le site d'appels équivaut au moins à enregistrer des informations et à générer le message sur le site d'appels. 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 messages 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 exception). Ainsi, si le message est généré lors de son lancement, un dérivé de std :: exception, comme std :: runtime_error, est requis en tant que classe de base. Si le message est généré lentement, std :: exception est la base appropriée.
Si toutes vos exceptions possibles dérivent de 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 méthode what
pour obtenir plus d'informations. Le C++ ne supporte pas le dactylographie, donc une autre classe avec une méthode what
nécessiterait une capture et un code différents pour l'utiliser.
Puisque le langage lance déjà std :: exception, vous devez quand même le comprendre pour fournir un rapport d'erreur correct. Vous pouvez également utiliser cette même capture pour toutes les exceptions imprévues de votre choix. En outre, presque toutes les bibliothèques qui lancent 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 définitivement meilleure.
Une autre raison des exceptions de sous-classe est un meilleur aspect de la conception lorsque vous travaillez sur de grands systèmes encapsulés. Vous pouvez le réutiliser pour des éléments tels que les messages de validation, les requêtes des utilisateurs, les erreurs irrécupérables du contrôleur, etc. Plutôt que de réécrire ou de reconfigurer toutes vos validations comme des messages, vous pouvez simplement les "récupérer" dans le fichier source principal, mais rejetez l'erreur n'importe où dans votre ensemble 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 génération de rapports le consignera, etc.
Bien que cette question soit plutôt ancienne et que de nombreuses réponses lui aient déjà été données, je souhaite simplement ajouter une note sur la manière de gérer correctement les exceptions en C++ 11, car cette question me manque constamment dans les discussions sur les exceptions:
std::nested_exception
et std::throw_with_nested
Il est décrit dans StackOverflow ici et ici , comment vous pouvez obtenir un suivi de vos exceptions dans votre code sans avoir besoin d'un débogueur ou d'une journalisation fastidieuse, en écrivant simplement un gestionnaire d'exceptions approprié. va redistribuer 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 à un tel backtrace! Vous pouvez également consulter mon MWE sur GitHub , où un backtrace ressemblerait à quelque chose comme 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"
std::runtime_error
pour obtenir beaucoup d'informations lorsqu'une exception est levée.Le seul avantage que je vois dans les sous-classes (au lieu d'utiliser simplement std::runtime_error
) est que votre gestionnaire d'exceptions peut capturer 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;
}