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 goto
ajustements 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?
la source
Réponses:
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
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::thread
oustd::thread
) lèveront une exception pour quitter correctement.É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.
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
new
peut 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), unstd::list::push_back
peut 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:
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:
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:
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:
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, ellet
n'est pas modifiée, donc les opérations 1 à 3 peuvent être considérées comme "fortes": si quelque chose se déclenche,t
n'est pas modifié etX
ne fuira pas car il appartient au pointeur intelligent.Ensuite, nous créons une copie
t2
det
et travaillons sur cette copie de l'opération 4 à 7. Si quelque chose se lève,t2
est modifié, maist
reste l'original. Nous offrons toujours la garantie solide.Ensuite, nous échangeons
t
ett2
. Les opérations de swap doivent être nothrow en C ++, alors espérons que le swap pourT
lequel 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
t
a sa valeur exceptée. S'il échoue, ilt
a 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":
swap()
fonctions écrites sur mesure. Il convient toutefois de noter que celastd::swap()
peut échouer en fonction des opérations qu'il utilise en internela valeur par défaut
std::swap
fera 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 pourvector
,deque
etlist
ne lancera pas, alors qu'elle le pourrait pourmap
si 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
Dionadar fait référence à la ligne suivante, qui a en effet un comportement indéfini.
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.
la source
"finally" does exactly what you were trying to achieve
Bien sûr que oui . Et comme il est facile de produire du code fragile avecfinally
, la notion "essayer avec des ressources" a finalement été introduite dans Java 7 (c'est-à-dire 10 ans après les C #using
et 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 #using
et Javatry
).RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimes
Non, ce n'est pas le cas. Vous pouvez utiliser des pointeurs intelligents ou des classes utilitaires pour "protéger" les ressources.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:
Personnellement, j'ai découvert ces articles assez tard, une grande partie de mon code C ++ n'est certainement pas à l'abri des exceptions.
la source
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.
la source
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.
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_ptr
pour la mémoire,boost::lock_guard
pour 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.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
/catch
blocks. 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 sanstry
/catch
blocs partout .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.
J'ai essayé quelques variations au fil des ans comme l'encodage des états dans les bits supérieurs (ala
HRESULT
s ) ou cet horriblesetjmp() ... 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:
try
/catch
quand vous pouvez faire quelque chose à propos d'une exception spécifiquenew
oudelete
en codestd::sprintf
,snprintf
et des tableaux en général - utilisationstd::ostringstream
pour le formatage et remplacer les tableaux avecstd::vector
etstd::string
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 .
la source
new
oudelete
du code": par raw je suppose que vous voulez dire en dehors d'un constructeur ou destructeur.delete
ne doit jamais être utilisé en dehors de l'implémentation detr1::shared_ptr
et similaires.new
peut être utilisé à condition que son utilisation ressemble à quelque chosetr1::shared_ptr<X> ptr(new X(arg, arg));
. La partie importante est que le résultat denew
va directement dans un pointeur géré. La page sur lesboost::shared_ptr
meilleures pratiques décrit le meilleur.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 .
la source
Eh bien, j'en ai certainement l'intention.
Je suis sûr que mes serveurs 24/7 construits à l'aide d'exceptions fonctionnent 24/7 et ne fuient pas la mémoire.
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
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.
la source
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.
la source
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.
la source
noexcept
.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.
la source
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.la source
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_InitializationJ'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?
la source
É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.]
la source
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.
la source
f = new foo(); f->doSomething(); delete f;
si la méthode doSomething lève une exception, vous avez une fuite de mémoire.