Écrivez-vous (vraiment) du code d'exception? [fermé]

312

La gestion des exceptions (EH) semble être la norme actuelle, et en recherchant sur le Web, je ne trouve pas de nouvelles idées ou méthodes qui tentent de l'améliorer ou de la remplacer (enfin, certaines variantes existent, mais rien de nouveau).

Bien que la plupart des gens semblent l'ignorer ou simplement l'accepter, EH a d' énormes inconvénients: les exceptions sont invisibles pour le code et il crée de très nombreux points de sortie possibles. Joel sur le logiciel a écrit un article à ce sujet . La comparaison des gotoajustements parfaits, cela m'a fait repenser à EH.

J'essaie d'éviter EH et j'utilise simplement des valeurs de retour, des rappels ou tout ce qui convient à l'objectif. Mais quand vous devez écrire du code fiable, vous ne pouvez tout simplement pas ignorer EH ces jours-ci : il commence par le new, qui peut lever une exception, au lieu de simplement retourner 0 (comme dans l'ancien temps). Cela rend toute ligne de code C ++ vulnérable à une exception. Et puis plus d'endroits dans le code fondateur C ++ lèvent des exceptions ... std lib le fait, et ainsi de suite.

On a l'impression de marcher sur des terrains instables . Alors, maintenant, nous sommes obligés de faire attention aux exceptions!

Mais c'est dur, c'est vraiment dur. Vous devez apprendre à écrire du code d'exception, et même si vous avez une certaine expérience avec lui, il sera toujours nécessaire de vérifier une seule ligne de code pour être sûr! Ou vous commencez à mettre des blocs try / catch partout, ce qui encombre le code jusqu'à ce qu'il atteigne un état d'illisibilité.

EH a remplacé l'ancienne approche déterministe propre (valeurs de retour ..), qui avait seulement quelques inconvénients compréhensibles et facilement résolubles par une approche qui crée de nombreux points de sortie possibles dans votre code, et si vous commencez à écrire du code qui intercepte les exceptions (ce que vous sont obligés de le faire à un moment donné), puis il crée même une multitude de chemins à travers votre code (code dans les blocs catch, pensez à un programme serveur où vous avez besoin de fonctionnalités de journalisation autres que std :: cerr ..). EH a des avantages, mais ce n'est pas le point.

Mes vraies questions:

  • Écrivez-vous vraiment du code d'exception?
  • Êtes-vous sûr que votre dernier code "prêt pour la production" est protégé contre les exceptions?
  • Pouvez-vous même être sûr que c'est le cas?
  • Connaissez-vous et / ou utilisez-vous réellement des alternatives qui fonctionnent?
Frunsi
la source
44
"EH a remplacé l'ancienne approche déterministe propre (valeurs de retour ..)" Quoi? Les exceptions sont tout aussi déterministes que les codes retour. Il n'y a rien d'aléatoire à leur sujet.
rlbond
2
EH est la norme depuis longtemps (depuis Ada même!)
12
"EH" est-il une abréviation établie pour la gestion des exceptions? Je ne l'ai jamais vu auparavant.
Thomas Padron-McCarthy
3
Les lois de Newton sont applicables aujourd'hui pour la plupart des choses. Le fait qu'ils aient prédit un très large éventail de phénomènes pendant des siècles est significatif.
David Thornley
4
@frunsi: Heh, désolé de vous avoir dérouté! Mais je pense que, si quoi que ce soit, un acronyme ou une abréviation étrange et inexpliquée découragera plus que jamais les gens.
Thomas Padron-McCarthy

Réponses:

520

Votre question fait une affirmation, que «l'écriture de code d'exception est très difficile». Je vais d'abord répondre à vos questions, puis répondre à la question cachée derrière elles.

Répondre à des questions

Écrivez-vous vraiment du code d'exception?

Bien sur que oui.

C'est la raison pour laquelle Java a perdu beaucoup de son attrait pour moi en tant que programmeur C ++ (manque de sémantique RAII), mais je m'égare: c'est une question C ++.

Il est en effet nécessaire lorsque vous devez travailler avec du code STL ou Boost. Par exemple, les threads C ++ ( boost::threadou std::thread) lèveront une exception pour quitter correctement.

Êtes-vous sûr que votre dernier code "prêt pour la production" est protégé contre les exceptions?

Pouvez-vous même être sûr que c'est le cas?

Écrire du code sans exception est comme écrire du code sans bogue.

Vous ne pouvez pas être sûr à 100% que votre code est protégé contre les exceptions. Mais ensuite, vous vous efforcez de le faire, en utilisant des modèles bien connus et en évitant les anti-modèles bien connus.

Connaissez-vous et / ou utilisez-vous réellement des alternatives qui fonctionnent?

Il n'y a pas d' alternative viable en C ++ (c'est-à-dire que vous devrez revenir en C et éviter les bibliothèques C ++, ainsi que les surprises externes comme Windows SEH).

Écriture d'un code d'exception sécurisé

Pour écrire du code d'exception, vous devez d' abord connaître le niveau de sécurité d'exception de chaque instruction que vous écrivez.

Par exemple, un newpeut lever une exception, mais l'attribution d'un intégré (par exemple un int ou un pointeur) n'échouera pas. Un échange n'échouera jamais (n'écrivez jamais un échange de lancer), un std::list::push_backpeut lancer ...

Garantie d'exception

La première chose à comprendre est que vous devez pouvoir évaluer la garantie d'exception offerte par l'ensemble de vos fonctions:

  1. aucun : votre code ne devrait jamais offrir cela. Ce code va tout fuir et tomber en panne à la toute première exception levée.
  2. basique : c'est la garantie que vous devez à tout le moins offrir, c'est-à-dire que si une exception est levée, aucune ressource n'est perdue et tous les objets sont toujours entiers
  3. strong : le traitement réussira ou lèvera une exception, mais s'il lève, les données seront dans le même état que si le traitement n'avait pas du tout démarré (cela donne un pouvoir transactionnel à C ++)
  4. nothrow / nofail : le traitement réussira.

Exemple de code

Le code suivant semble être C ++ correct, mais en vérité, offre la garantie "none", et donc, il n'est pas correct:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

J'écris tout mon code avec ce genre d'analyse à l'esprit.

La garantie la plus basse offerte est basique, mais ensuite, l'ordre de chaque instruction rend la fonction entière "aucune", car si 3. lance, x fuira.

La première chose à faire serait de rendre la fonction "basique", c'est-à-dire de mettre x dans un pointeur intelligent jusqu'à ce qu'il soit détenu en toute sécurité par la liste:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Désormais, notre code offre une garantie "basique". Rien ne fuira et tous les objets seront dans un état correct. Mais nous pourrions offrir plus, c'est-à-dire la garantie solide. C'est là que cela peut devenir coûteux, et c'est pourquoi tout le code C ++ n'est pas fort. Essayons:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Nous avons réorganisé les opérations, en créant d'abord et en les réglant Xà leur juste valeur. Si une opération échoue, elle tn'est pas modifiée, donc les opérations 1 à 3 peuvent être considérées comme "fortes": si quelque chose se déclenche, tn'est pas modifié et Xne fuira pas car il appartient au pointeur intelligent.

Ensuite, nous créons une copie t2de tet travaillons sur cette copie de l'opération 4 à 7. Si quelque chose se lève, t2est modifié, mais treste l'original. Nous offrons toujours la garantie solide.

Ensuite, nous échangeons tet t2. Les opérations de swap doivent être nothrow en C ++, alors espérons que le swap pour Tlequel vous avez écrit est nothrow (si ce n'est pas le cas, réécrivez-le pour qu'il ne soit pas throw).

Donc, si nous atteignons la fin de la fonction, tout a réussi (pas besoin d'un type de retour) et ta sa valeur exceptée. S'il échoue, il ta toujours sa valeur d'origine.

Maintenant, offrir la garantie forte pourrait être assez coûteux, alors ne vous efforcez pas d'offrir la garantie forte à tout votre code, mais si vous pouvez le faire sans coût (et l'incrustation C ++ et d'autres optimisations pourraient rendre tout le code ci-dessus gratuit) puis faites-le. L'utilisateur de la fonction vous en remerciera.

Conclusion

Il faut une certaine habitude pour écrire du code sans exception. Vous devrez évaluer la garantie offerte par chaque instruction que vous utiliserez, puis vous devrez évaluer la garantie offerte par une liste d'instructions.

Bien sûr, le compilateur C ++ ne sauvegardera pas la garantie (dans mon code, j'offre la garantie en tant que balise doxygen @warning), ce qui est un peu triste, mais cela ne devrait pas vous empêcher d'essayer d'écrire du code sans exception.

Échec normal vs bug

Comment un programmeur peut-il garantir qu'une fonction sans échec réussira toujours? Après tout, la fonction pourrait avoir un bug.

C'est vrai. Les garanties d'exception sont censées être offertes par un code sans bogue. Mais alors, dans n'importe quel langage, appeler une fonction suppose que la fonction est exempte de bogues. Aucun code sain ne se protège contre la possibilité d'un bogue. Écrivez le code du mieux que vous pouvez, puis offrez la garantie en supposant qu'il est exempt de bogues. Et s'il y a un bug, corrigez-le.

Les exceptions concernent les échecs de traitement exceptionnels, pas les bogues de code.

Derniers mots

Maintenant, la question est "Est-ce que ça vaut le coup?".

Bien sûr que oui. Avoir une fonction "nothrow / no-fail" sachant que la fonction n'échouera pas est une grande aubaine. La même chose peut être dite pour une fonction "forte", qui vous permet d'écrire du code avec une sémantique transactionnelle, comme les bases de données, avec des fonctionnalités de validation / restauration, la validation étant l'exécution normale du code, la levée d'exceptions étant la restauration.

Ensuite, le «basique» est la moindre garantie que vous devriez offrir. Le C ++ est un langage très puissant, avec ses étendues, vous permettant d'éviter toute fuite de ressources (quelque chose qu'un garbage collector aurait du mal à offrir pour la base de données, la connexion ou les descripteurs de fichiers).

Donc, pour autant que je le vois, il est la peine.

Edit 2010-01-29: A propos du swap sans lancer

nobar a fait un commentaire qui, je pense, est tout à fait pertinent, car il fait partie de "comment écrivez-vous le code d'exception":

  • [moi] Un échange n'échouera jamais (n'écrivez même pas un échange de lancement)
  • [nobar] Ceci est une bonne recommandation pour les swap()fonctions écrites sur mesure. Il convient toutefois de noter que cela std::swap()peut échouer en fonction des opérations qu'il utilise en interne

la valeur par défaut std::swapfera des copies et des affectations qui, pour certains objets, peuvent être lancées. Ainsi, le swap par défaut peut être lancé, utilisé pour vos classes ou même pour les classes STL. En ce qui concerne la norme C ++, l'opération de permutation pour vector, dequeet listne lancera pas, alors qu'elle le pourrait pour mapsi le foncteur de comparaison peut lancer sur la construction de copie (voir The C ++ Programming Language, Special Edition, appendix E, E.4.3 .Échanger ).

En regardant l'implémentation Visual C ++ 2008 du swap du vecteur, le swap du vecteur ne lancera pas si les deux vecteurs ont le même allocateur (c'est-à-dire le cas normal), mais fera des copies s'ils ont des allocateurs différents. Et donc, je suppose qu'il pourrait jeter dans ce dernier cas.

Ainsi, le texte original tient toujours: N'écrivez jamais un échange de lancement, mais le commentaire de nobar doit être rappelé: Assurez-vous que les objets que vous échangez ont un échange sans lancement.

Edit 2011-11-06: article intéressant

Dave Abrahams , qui nous a donné les garanties de base / fort / non-retour , a décrit dans un article son expérience sur la sécurisation de l'exception STL:

http://www.boost.org/community/exception_safety.html

Regardez le 7ème point (tests automatisés pour la sécurité des exceptions), où il s'appuie sur des tests unitaires automatisés pour s'assurer que chaque cas est testé. Je suppose que cette partie est une excellente réponse à la question de l'auteur " Pouvez-vous même en être sûr? ".

Edit 2013-05-31: Commentaire de Dionadar

t.integer += 1;est sans la garantie qu'aucun débordement ne se produira PAS sans exception, et peut en fait invoquer techniquement UB! (Le débordement signé est UB: C ++ 11 5/4 "Si, lors de l'évaluation d'une expression, le résultat n'est pas défini mathématiquement ou n'est pas dans la plage de valeurs représentables pour son type, le comportement n'est pas défini.") Notez que non signé les entiers ne débordent pas, mais effectuent leurs calculs dans une classe d'équivalence modulo 2 ^ # bits.

Dionadar fait référence à la ligne suivante, qui a en effet un comportement indéfini.

   t.integer += 1 ;                 // 1. nothrow/nofail

La solution ici est de vérifier si l'entier est déjà à sa valeur maximale (en utilisant std::numeric_limits<T>::max()) avant de faire l'ajout.

Mon erreur irait dans la section "Échec normal contre bogue", c'est-à-dire un bogue. Cela n'invalide pas le raisonnement, et cela ne signifie pas que le code d'exception est inutile car impossible à atteindre. Vous ne pouvez pas vous protéger contre l'extinction de l'ordinateur, les bogues du compilateur, ou même vos bogues, ou d'autres erreurs. Vous ne pouvez pas atteindre la perfection, mais vous pouvez vous rapprocher le plus possible.

J'ai corrigé le code en gardant à l'esprit le commentaire de Dionadar.

paercebal
la source
6
Merci beaucoup! J'espérais toujours quelque chose de différent. Mais j'accepte votre réponse pour vous en tenir au C ++ et pour votre explication détaillée. Vous avez même réfuté mon infraction "Cela rend toute ligne de code C ++ vulnérable à une exception". Cette analyse ligne par ligne a du sens après tout ... Je dois y penser.
Frunsi
8
"Un échange n'échouera jamais". Il s'agit d'une bonne recommandation pour les fonctions swap () personnalisées. Il convient de noter, cependant, que std :: swap () peut échouer en fonction des opérations qu'il utilise en interne.
nobar
7
@Mehrdad:: "finally" does exactly what you were trying to achieveBien sûr que oui . Et comme il est facile de produire du code fragile avec finally, la notion "essayer avec des ressources" a finalement été introduite dans Java 7 (c'est-à-dire 10 ans après les C # usinget 3 décennies après les destructeurs C ++). C'est cette fragilité que je critique. Quant à Just because [finally] doesn't match your taste (RAII doesn't match mine, [...]) doesn't mean it's "failing": En cela, l'industrie n'est pas d'accord avec votre goût, car les langages Garbage Collected ont tendance à ajouter des déclarations inspirées de RAII (C # usinget Java try).
paercebal
5
@Mehrdad:: RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimesNon, ce n'est pas le cas. Vous pouvez utiliser des pointeurs intelligents ou des classes utilitaires pour "protéger" les ressources.
paercebal
5
@Mehrdad: "cela vous oblige à créer un wrapper pour quelque chose pour lequel vous n'avez peut-être pas besoin d'un wrapper" - lors du codage à la manière RAII, vous n'aurez besoin d'aucun type de wrappers: la ressource est l'objet et la durée de vie des objets est la durée de vie de la ressource. Cependant, moi venant d'un monde C ++, j'ai actuellement du mal avec un projet Java. Comparé à RAII en C ++, cette "gestion manuelle des ressources" en Java EST UN MESS! Mon avis, mais Java a depuis longtemps échangé "mgmt de mémoire automatique" pour "mgmt de ressource automatique".
Frunsi
32

L'écriture de code sans exception en C ++ ne consiste pas tant à utiliser beaucoup de blocs try {} catch {}. Il s'agit de documenter le type de garanties fournies par votre code.

Je recommande de lire la série Guru Of The Week de Herb Sutter , en particulier les épisodes 59, 60 et 61.

Pour résumer, vous pouvez offrir trois niveaux de sécurité d'exception:

  • De base: lorsque votre code lève une exception, votre code ne perd pas de ressources et les objets restent destructibles.
  • Strong: Lorsque votre code lève une exception, il laisse l'état de l'application inchangé.
  • No throw: votre code ne lève jamais d'exceptions.

Personnellement, j'ai découvert ces articles assez tard, une grande partie de mon code C ++ n'est certainement pas à l'abri des exceptions.

Joh
la source
1
Son livre "Exceptional C ++" est également une bonne lecture. Mais j'essaie toujours de remettre en question le concept d'EH ...
Frunsi
8
+1 OP combine la gestion des exceptions (les attraper) avec la sécurité des exceptions (généralement plus sur RAII)
jk.
Je soupçonne que très peu de code C ++ de production est à l'abri des exceptions.
Raedwald
18

Certains d'entre nous utilisent l'exception depuis plus de 20 ans. PL / I les a, par exemple. La prémisse qu'il s'agit d'une technologie nouvelle et dangereuse me semble discutable.

bmargulies
la source
S'il vous plaît, ne vous méprenez pas, je suis (ou j'essaie de) remettre en question EH. Et surtout C ++ EH. Et je cherche des alternatives. Peut-être que je dois l'accepter (et je le ferai si c'est la seule façon), mais je pense qu'il pourrait y avoir de meilleures alternatives. Ce n'est pas que je pense que le concept est nouveau, mais oui, je pense qu'il peut être plus dangereux qu'une gestion d'erreur explicite avec des codes retour ...
Frunsi
1
Si vous ne l'aimez pas, ne l'utilisez pas. Mettez des blocs d'essai autour des choses que vous devez appeler qui peuvent lancer et réinventer le code d'erreur, et souffrir des problèmes qu'il a, qui dans certains cas sont tolérables.
bmargulies
Ok, parfait, je vais juste utiliser EH et codes d'erreur, et vivre avec. Je suis un imbécile, j'aurais dû arriver à cette solution par moi-même! ;)
Frunsi
17

Tout d'abord (comme l'a déclaré Neil), SEH est le traitement structuré des exceptions de Microsoft. Il est similaire mais non identique au traitement des exceptions en C ++. En fait, vous devez activer la gestion des exceptions C ++ si vous le souhaitez dans Visual Studio - le comportement par défaut ne garantit pas que les objets locaux sont détruits dans tous les cas! Dans les deux cas, la gestion des exceptions n'est pas vraiment plus difficile, elle est juste différente .

Maintenant pour vos questions réelles.

Écrivez-vous vraiment du code d'exception?

Oui. Je m'efforce d'obtenir un code d'exception sûr dans tous les cas. J'évangélise en utilisant des techniques RAII pour un accès limité aux ressources (par exemple, boost::shared_ptrpour la mémoire, boost::lock_guardpour le verrouillage). En général, l'utilisation cohérente de RAII et des techniques de protection de portée rendra le code de sécurité des exceptions beaucoup plus facile à écrire. L'astuce consiste à savoir ce qui existe et comment l'appliquer.

Êtes-vous sûr que votre dernier code "prêt pour la production" est protégé contre les exceptions?

Non, c'est aussi sûr que ça. Je peux dire que je n'ai pas vu de défaut de processus en raison d'une exception depuis plusieurs années d'activité 24/7. Je ne m'attends pas à un code parfait, juste un code bien écrit. En plus de fournir une sécurité exceptionnelle, les techniques ci-dessus garantissent l'exactitude d'une manière qui est presque impossible à réaliser avec try/ catchblocks. Si vous interceptez tout dans votre portée de contrôle supérieure (thread, processus, etc.), vous pouvez être sûr que vous continuerez à fonctionner malgré les exceptions (la plupart du temps ). Les mêmes techniques vous aideront également à continuer à fonctionner correctement face aux exceptions sans try/ catchblocs partout .

Pouvez-vous même en être sûr?

Oui. Vous pouvez être sûr par un audit approfondi du code, mais personne ne le fait vraiment? Cependant, des révisions régulières du code et des développeurs prudents sont très utiles.

Connaissez-vous et / ou utilisez-vous réellement des alternatives qui fonctionnent?

J'ai essayé quelques variations au fil des ans comme l'encodage des états dans les bits supérieurs (ala HRESULTs ) ou cet horrible setjmp() ... longjmp()hack. Ces deux éléments se décomposent dans la pratique, mais de manières complètement différentes.


En fin de compte, si vous prenez l'habitude d'appliquer quelques techniques et de réfléchir soigneusement à l'endroit où vous pouvez réellement faire quelque chose en réponse à une exception, vous vous retrouverez avec un code très lisible et sans danger pour les exceptions. Vous pouvez résumer cela en suivant ces règles:

  • Vous voulez seulement voir try/ catchquand vous pouvez faire quelque chose à propos d'une exception spécifique
  • Vous ne voulez presque jamais voir un code brut newou deleteen code
  • Évitez l' std::sprintf, snprintfet des tableaux en général - utilisation std::ostringstreampour le formatage et remplacer les tableaux avecstd::vector etstd::string
  • En cas de doute, recherchez les fonctionnalités dans Boost ou STL avant de lancer la vôtre

Je ne peux que vous recommander d'apprendre à utiliser correctement les exceptions et d'oublier les codes de résultat si vous prévoyez d'écrire en C ++. Si vous souhaitez éviter les exceptions, vous pouvez envisager d'écrire dans une autre langue qui ne les possède pas ou les sécurise . Si vous voulez vraiment apprendre à utiliser pleinement C ++, lisez quelques livres de Herb Sutter , Nicolai Josuttis et Scott Meyers .

D.Shawley
la source
«le comportement par défaut ne garantit pas que les objets locaux sont détruits dans tous les cas» Vous dites que le paramètre par défaut du compilateur Visual Studio C ++ produit du code incorrect face aux exceptions. En est-il vraiment ainsi?
Raedwald
"Vous ne voulez presque jamais voir un raw newou deletedu code": par raw je suppose que vous voulez dire en dehors d'un constructeur ou destructeur.
Raedwald
@Raedwald - re: VC ++: l'édition VS2005 de VC ++ ne détruira pas les objets locaux lorsqu'une exception SEH est levée. Lisez le "activer la gestion des exceptions C ++" . Dans VS2005, les exceptions SEH n'appellent pas les destructeurs d'objets C ++ par défaut. Si vous appelez des fonctions Win32 ou quoi que ce soit défini dans une DLL d'interface C, vous devez vous en préoccuper car ils peuvent (et occasionnellement) lancer une exception SEH à votre façon.
D.Shawley
@Raedwald: re: raw : fondamentalement, deletene doit jamais être utilisé en dehors de l'implémentation de tr1::shared_ptret similaires. newpeut être utilisé à condition que son utilisation ressemble à quelque chose tr1::shared_ptr<X> ptr(new X(arg, arg));. La partie importante est que le résultat de newva directement dans un pointeur géré. La page sur les boost::shared_ptrmeilleures pratiques décrit le meilleur.
D.Shawley
Pourquoi faites-vous référence à std :: sprintf (et al) dans votre ensemble de règles pour la sécurité des exceptions? Ceux-ci ne documentent pas qu'ils lèvent des
mabraham
10

Il n'est pas possible d'écrire du code sans exception sous l'hypothèse que "n'importe quelle ligne peut lancer". La conception d'un code sans exception repose de manière critique sur certains contrats / garanties que vous êtes censé attendre, observer, suivre et implémenter dans votre code. Il est absolument nécessaire d'avoir un code qui est garanti de ne jamais lancer. Il existe d'autres types de garanties d'exception.

En d'autres termes, la création d'un code sans exception est, dans une large mesure, une question de conception de programme et pas seulement une question de codage simple .

Fourmi
la source
8
  • Écrivez-vous vraiment du code d'exception?

Eh bien, j'en ai certainement l'intention.

  • Êtes-vous sûr que votre dernier code "prêt pour la production" est protégé contre les exceptions?

Je suis sûr que mes serveurs 24/7 construits à l'aide d'exceptions fonctionnent 24/7 et ne fuient pas la mémoire.

  • Pouvez-vous même être sûr que c'est le cas?

Il est très difficile d'être sûr qu'un code est correct. En règle générale, on ne peut que se fier aux résultats

  • Connaissez-vous et / ou utilisez-vous réellement des alternatives qui fonctionnent?

Non. L'utilisation d'exceptions est plus propre et plus facile que toutes les alternatives que j'ai utilisées au cours des 30 dernières années dans la programmation.

anon
la source
30
Cette réponse n'a aucune valeur.
Matt Joiner
6
@MattJoiner La question n'a alors aucune valeur.
Miles Rout
5

En laissant de côté la confusion entre les exceptions SEH et C ++, vous devez être conscient que les exceptions peuvent être levées à tout moment et écrire votre code en gardant cela à l'esprit. Le besoin de sécurité d'exception est en grande partie ce qui motive l'utilisation de RAII, des pointeurs intelligents et d'autres techniques C ++ modernes.

Si vous suivez les modèles bien établis, l'écriture de code sans exception n'est pas particulièrement difficile, et en fait, c'est plus facile que d'écrire du code qui gère correctement les retours d'erreur dans tous les cas.

Mark Bessey
la source
3

EH est bon, en général. Mais l'implémentation de C ++ n'est pas très conviviale car il est vraiment difficile de dire à quel point votre couverture de capture d'exception est bonne. Java, par exemple, rend cela facile, le compilateur aura tendance à échouer si vous ne gérez pas les exceptions possibles.

Mr. Boy
la source
2
C'est beaucoup mieux avec une utilisation judicieuse de noexcept.
einpoklum
2

J'aime bien travailler avec Eclipse et Java (nouveau pour Java), car cela génère des erreurs dans l'éditeur si vous manquez un gestionnaire EH. Cela rend les choses beaucoup plus difficiles à oublier pour gérer une exception ...

De plus, avec les outils IDE, il ajoute automatiquement le bloc try / catch ou un autre bloc catch.

Crowe T. Robot
la source
5
C'est la différence entre les exceptions vérifiées (Java) et non vérifiées (c ++) (Java en a aussi quelques-unes). L'avantage des exceptions vérifiées est ce que vous avez écrit, mais là, il a ses propres inconvénients. Google pour les différences d'approches et les différents problèmes qu'ils ont.
David Rodríguez - dribeas
2

Certains d'entre nous préfèrent des langages comme Java qui nous obligent à déclarer toutes les exceptions levées par les méthodes, au lieu de les rendre invisibles comme en C ++ et C #.

Lorsqu'elles sont effectuées correctement, les exceptions sont supérieures aux codes de retour d'erreur, si pour aucune autre raison que vous n'avez à propager manuellement les échecs dans la chaîne d'appels.

Cela étant dit, la programmation de bibliothèque d'API de bas niveau devrait probablement éviter la gestion des exceptions et s'en tenir aux codes de retour d'erreur.

D'après mon expérience, il est difficile d'écrire du code de gestion d'exceptions propre en C ++. Je finis par en utiliser new(nothrow)beaucoup.

David R Tribble
la source
4
Et vous évitez la plupart de la bibliothèque standard? L'utilisation new(std::nothrow)ne suffit pas. Soit dit en passant, il est plus facile d'écrire du code à l'exception des exceptions en C ++ qu'en Java: en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
avakar
3
La convivialité des exceptions vérifiées par Java est très exagérée. En fait, les langages non Java, ils ne sont PAS considérés comme un succès. C'est pourquoi l'instruction "throw" en C ++ est désormais considérée comme obsolète, et C # n'a jamais sérieusement envisagé de les implémenter (c'était un choix de conception). Pour Java, le document suivant pourrait être instructif: googletesting.blogspot.com/2009/09/…
paercebal
2
D'après mon expérience, l'écriture de code sans exception en C ++ n'est pas si difficile et cela tend à conduire à un code plus propre en général. Bien sûr, vous devez apprendre à le faire.
David Thornley
2

J'essaie de mon mieux pour écrire du code sans exception, oui.

Cela signifie que je prends soin de garder un œil sur les lignes qui peuvent lancer. Tout le monde ne peut pas, et il est extrêmement important de garder cela à l'esprit. L'essentiel est vraiment de penser, et de concevoir votre code pour satisfaire, les garanties d'exception définies dans la norme.

Cette opération peut-elle être écrite pour fournir la garantie d'exception forte? Dois-je me contenter de la base? Quelles lignes peuvent lever des exceptions et comment puis-je m'assurer que si elles le font, elles ne corrompent pas l'objet?

jalf
la source
2
  • Écrivez-vous vraiment du code d'exception? [Il n'y a rien comme ça. Les exceptions sont un bouclier papier contre les erreurs, sauf si vous disposez d'un environnement géré. Cela s'applique aux trois premières questions.]

  • Connaissez-vous et / ou utilisez-vous réellement des alternatives qui fonctionnent? [Alternative à quoi? Le problème ici est que les gens ne séparent pas les erreurs réelles du fonctionnement normal du programme. Si c'est un fonctionnement normal du programme (c'est-à-dire un fichier introuvable), ce n'est pas vraiment une gestion des erreurs. S'il s'agit d'une erreur réelle, il n'y a aucun moyen de la «gérer» ou ce n'est pas une erreur réelle. Votre objectif ici est de découvrir ce qui n'a pas fonctionné et d'arrêter la feuille de calcul et de consigner une erreur, de redémarrer le pilote sur votre grille-pain ou de simplement prier pour que le chasseur à réaction puisse continuer à voler même lorsque son logiciel est bogué et qu'il espère le meilleur.]

Fromage Charles Eli
la source
0

Beaucoup de gens (je dirais même la plupart) le font.

Ce qui est vraiment important à propos des exceptions, c'est que si vous n'écrivez aucun code de manipulation - le résultat est parfaitement sûr et bien comporté. Trop impatient de paniquer, mais sûr.

Vous devez faire activement des erreurs dans les gestionnaires pour obtenir quelque chose de dangereux, et seul catch (...) {} sera comparable à ignorer le code d'erreur.

ima
la source
4
Pas vrai. Il est très facile d'écrire du code qui n'est pas protégé contre les exceptions. Par exemple: f = new foo(); f->doSomething(); delete f;si la méthode doSomething lève une exception, vous avez une fuite de mémoire.
Kristopher Johnson
1
Cela n'aurait pas d'importance lorsque votre programme se termine, non? Pour continuer l'exécution, vous devrez avaler activement les exceptions. Eh bien, il existe des cas spécifiques où l'arrêt sans nettoyage est toujours inacceptable, mais de telles situations nécessitent une attention particulière dans n'importe quel langage et style de programmation.
ima
Vous ne pouvez tout simplement pas ignorer les exceptions (et ne pas écrire de code de gestion), ni en C ++ ni en code managé. Ce sera dangereux et cela ne se comportera pas bien. Sauf peut-être un code de jouet.
Frunsi
1
Si vous ignorez les exceptions dans le code d'application, le programme peut toujours ne pas bien se comporter lorsque des ressources externes sont impliquées. Certes, le système d'exploitation se soucie de la fermeture des poignées de fichiers, des verrous, des sockets, etc. Mais tout n'est pas géré, par exemple, il peut laisser des fichiers inutiles ou endommager les fichiers lors de l'écriture, etc. Si vous ignorez les exceptions, vous avez un problème en Java, en C ++ vous pouvez travailler avec RAII (mais lorsque vous utilisez la technique RAII, vous les utilisez probablement parce que vous vous souciez des exceptions) ...
Frunsi
3
S'il vous plaît, ne tordez pas mes mots. J'ai écrit "si vous n'écrivez aucun code de manipulation" - et je voulais dire exactement cela. Pour ignorer les exceptions, vous devez écrire du code.
ima