Si vous êtes dans le camp «nous n’utilisons pas les exceptions», alors comment utilisez-vous la bibliothèque standard?

Note: je ne joue pas à l’avocat du diable ou quelque chose comme ça ici – je suis vraiment curieux puisque je ne suis pas dans ce camp moi-même.

La plupart des types de la bibliothèque standard ont soit des fonctions de mutation pouvant générer des exceptions (par exemple, en cas d’échec de l’allocation de mémoire), soit des fonctions non mutantes pouvant générer des exceptions (par exemple, des accesseurs indexés hors limites). De plus, de nombreuses fonctions gratuites peuvent générer des exceptions (par exemple, operator new et dynamic_cast ).

Comment gérez-vous pratiquement cela dans le contexte de “nous n’utilisons pas les exceptions”?

  • Essayez-vous de ne jamais appeler une fonction capable de lancer? (Je ne peux pas voir à quel point cela pourrait évoluer, alors je suis très intéressé d’entendre ce que vous faites si tel est le cas)

  • Êtes-vous d’accord avec le lancer de bibliothèque standard et vous traitez “nous n’utilisons pas d’exceptions” car “nous ne lançons jamais d’exceptions de notre code et nous n’attrapons jamais les exceptions du code des autres “?

  • Désactivez-vous la gestion des exceptions via les commutateurs du compilateur? Si oui, comment fonctionnent les parties de la bibliothèque standard qui lancent des exceptions?

  • EDIT Vos constructeurs peuvent-ils échouer ou, par convention, utilisez-vous une construction en deux étapes avec une fonction d’initialisation dédiée qui peut renvoyer un code d’erreur en cas d’échec (ce que le constructeur ne peut pas) ou faites-vous autre chose?

EDIT Clarification mineure 1 semaine après le lancement de la question … Une grande partie du contenu des commentaires et des questions ci-dessous porte sur les raisons pour lesquelles les exceptions sont opposées à “autre chose”. Mon intérêt n’est pas là-dedans, mais quand vous choisissez de faire “autre chose”, comment gérez-vous les parties standard de la bibliothèque qui génèrent des exceptions?

Je vais répondre pour moi et mon coin du monde. J’écris c ++ 14 (il y en aura 17 une fois que les compilateurs auront un meilleur support) des applications financières critiques pour la latence qui traitent des montants énormes et ne peuvent jamais baisser. Le jeu de règles est le suivant:

  • aucune exception
  • pas de rtti
  • aucune dépêche d’exécution
  • (presque) pas d’inheritance

La mémoire est regroupée et pré-allouée, il n’y a donc pas d’appels malloc après l’initialisation. Les structures de données sont soit immortelles ou sortingvialement copiables, de sorte que les destructeurs sont quasiment absents (il existe des exceptions, telles que les protecteurs de la scope). Fondamentalement, nous faisons de la sécurité de type C + + templates + lambdas. Bien sûr, les exceptions sont désactivées via le commutateur du compilateur. Comme pour la STL, les bonnes parties (algorithme, numérique, type_traits, iterator, atomic, …) sont utilisables. Les parties qui lancent les exceptions coïncident parfaitement avec les parties d’allocation de mémoire d’exécution et les parties semi-OO, ce qui nous permet de nous débarrasser de tous les problèmes: stream, conteneurs sauf std :: array, std :: ssortingng.

Pourquoi faire ceci?

  1. Parce que, comme OO, l’exception offre une propreté illusoire en cachant ou en déplaçant le problème ailleurs, et rend le diagnostic du programme plus difficile. Lorsque vous comstackz sans “-fno-exceptions”, toutes vos fonctions propres et bien comscopes doivent être suspectées d’être disponibles. Il est beaucoup plus facile d’avoir une vérification approfondie du périmètre de votre base de code que de rendre une opération plus efficace.
  2. Parce que les exceptions sont essentiellement des GOTO à longue scope qui ont une destination non spécifiée. Vous n’utiliserez pas longjmp (), mais les exceptions sont sans doute bien pires.
  3. Parce que les codes d’erreur sont supérieurs. Vous pouvez utiliser [[nodiscard]] pour forcer le code à vérifier.
  4. Parce que les hiérarchies d’exception sont inutiles. La plupart du temps, il est peu judicieux de distinguer ce qui est erroné, et quand c’est le cas, c’est probablement parce que différentes erreurs nécessitent un nettoyage différent et qu’il aurait été préférable de signaler explicitement.
  5. Parce que nous avons des invariants complexes à maintenir. Cela signifie qu’il existe des codes, même au plus profond des entrailles, qui nécessitent des garanties transnationales. Il y a deux façons de le faire: soit vous faites vos procédures impératives aussi pures que possible (par exemple: assurez-vous de ne jamais échouer), soit vous avez des structures de données immuables (par exemple, rendre la récupération d’échec possible). Si vous avez des structures de données immuables, vous pouvez bien sûr avoir des exceptions, mais vous ne les utiliserez pas car vous utiliserez des types de sum. Cependant, les structures de données fonctionnelles sont lentes, donc l’autre alternative est d’avoir des fonctions pures et de le faire dans un langage exempt d’exceptions tel que C, no-except C ++ ou Rust. Quel que soit le look de D, tant qu’il n’est pas nettoyé du GC et des exceptions, c’est une non-option.
  6. Avez-vous déjà testé vos exceptions comme vous le feriez avec un chemin de code explicite? Qu’en est-il des exceptions qui “ne peuvent jamais arriver”? Bien sûr, vous ne le faites pas, et lorsque vous atteignez réellement ces exceptions, vous êtes foutu.
  7. J’ai vu du “beau” code neutre d’exception en C ++. En d’autres termes, il fonctionne de manière optimale sans cas d’arête, que le code qu’il appelle utilise des exceptions ou non. Ils sont vraiment difficiles à écrire et je soupçonne qu’ils sont difficiles à modifier si vous souhaitez conserver toutes vos garanties d’exception. Cependant, je n’ai pas vu de “beau” code qui lève ou attrape des exceptions. Tous les codes que j’ai vus qui interagissent directement avec les exceptions ont été universellement laids. La quantité d’effort qui a été nécessaire pour écrire du code neutre par rapport aux exceptions a complètement dépassé la quantité d’effort économisée par le code de merde qui lance ou attrape des exceptions. “Beautiful” est entre guillemets parce que ce n’est pas une beauté réelle: il est généralement fossilisé car son édition exige le fardeau supplémentaire du maintien de la neutralité des exceptions. Si vous ne disposez pas de tests unitaires qui utilisent délibérément et de manière exhaustive les exceptions pour déclencher ces cas extrêmes, même le «beau» code neutre en termes d’exceptions se transforme en fumier.

Dans notre cas, nous désactivons les exceptions via le compilateur (par exemple, -fno-exceptions pour gcc).

Dans le cas de gcc, ils utilisent une macro appelée _GLIBCXX_THROW_OR_ABORT qui est définie comme

 #ifndef _GLIBCXX_THROW_OR_ABORT # if __cpp_exceptions # define _GLIBCXX_THROW_OR_ABORT(_EXC) (throw (_EXC)) # else # define _GLIBCXX_THROW_OR_ABORT(_EXC) (__builtin_abort()) # endif #endif 

(vous pouvez le trouver dans libstdc++-v3/include/bits/c++config sur les dernières versions de gcc).

Ensuite, il suffit de gérer le fait que les exceptions lancées ne sont que des avortements. Vous pouvez toujours attraper le signal et imprimer la stack (il y a une bonne réponse sur SO qui l’explique), mais vous feriez mieux d’éviter ce genre de choses (du moins dans les versions).

Si vous voulez un exemple, au lieu d’avoir quelque chose comme

 try { Foo foo = mymap.at("foo"); // ... } catch (std::exception& e) {} 

tu peux faire

 auto it = mymap.find("foo"); if (it != mymap.end()) { Foo foo = it->second; // ... } 

Je tiens également à souligner que lorsque vous vous interrogez sur le fait de ne pas utiliser les exceptions, il existe une question plus générale sur la bibliothèque standard: utilisez -vous une bibliothèque standard lorsque vous êtes dans l’un des camps “nous n’utilisons pas d’exceptions”?

La bibliothèque standard est lourde. Dans certains camps «nous n’utilisons pas d’exceptions», comme de nombreuses sociétés GameDev par exemple, des alternatives mieux adaptées au STL sont utilisées, principalement basées sur EASTL ou TTL. Ces bibliothèques n’utilisent pas les exceptions de toute façon et c’est parce que les consoles de huitième génération ne les ont pas bien traitées (ou même pas du tout). Pour un code de production AAA à la pointe de la technologie, les exceptions sont trop lourdes, ce qui est un scénario gagnant-gagnant.

En d’autres termes, pour de nombreux programmeurs, la désactivation des exceptions va de pair avec l’absence de STL.

Note j’utilise des exceptions … mais j’ai été obligé de ne pas le faire.

Essayez-vous de ne jamais appeler une fonction capable de lancer? (Je ne peux pas voir à quel point cela pourrait évoluer, alors je suis très intéressé d’entendre ce que vous faites si tel est le cas)

Cela serait probablement irréalisable, du moins à grande échelle. De nombreuses fonctions peuvent être lancées, évitez-les complètement paralyser votre base de code.

Êtes-vous d’accord avec le lancer de bibliothèque standard et vous traitez “nous n’utilisons pas d’exceptions” car “nous ne lançons jamais d’exceptions de notre code et nous n’attrapons jamais les exceptions du code des autres”?

Vous devez être d’accord avec ça … Si le code de la bibliothèque va lancer une exception et que votre code ne va pas le gérer, la terminaison est le comportement par défaut.

Désactivez-vous la gestion des exceptions via les commutateurs du compilateur? Si oui, comment fonctionnent les parties de la bibliothèque standard

Ceci est possible (il était parfois très populaire pour certains types de projets); les compilateurs le font / peuvent le faire, mais vous devrez consulter leur documentation pour savoir quels seraient ou pourraient être les résultats (et quelles fonctionnalités linguistiques sont sockets en charge dans ces conditions).

En général, lorsqu’une exception serait levée, le programme devrait abandonner ou quitter. Certaines normes de codage l’exigent encore, la norme de codage JSF vient à l’esprit (IIRC).

Stratégie générale pour ceux qui “n’utilisent pas les exceptions”

La plupart des fonctions ont un ensemble de conditions préalables qui peuvent être vérifiées avant que l’appel soit effectué. Vérifiez pour ceux S’ils ne sont pas rencontrés, alors ne faites pas l’appel; revenir à la gestion des erreurs dans ce code. Pour les fonctions que vous ne pouvez pas vérifier pour vous assurer que les conditions préalables sont remplies … pas grand-chose, le programme sera probablement interrompu.

Vous pourriez chercher à éviter les bibliothèques qui lancent des exceptions – vous l’avez demandé dans le contexte de la bibliothèque standard, cela ne correspond pas tout à fait à la facture, mais cela rest une option.

Autres stratégies possibles Je sais que cela semble banal, mais choisissez un langage qui ne les utilise pas. C pourrait bien faire …

… point crucial de votre question (votre interaction avec la bibliothèque standard, le cas échéant), je suis très intéressé par vos constructeurs. Peuvent-ils échouer ou, par convention, utilisez-vous une construction en deux étapes avec une fonction d’initialisation dédiée qui peut renvoyer un code d’erreur en cas d’échec (ce que le constructeur ne peut pas)? Ou quelle est votre stratégie là-bas?

Si les constructeurs sont utilisés, il existe généralement deux approches pour indiquer la défaillance;

  1. Définissez un code d’erreur interne ou un enum pour indiquer l’échec et la défaillance. Cela peut être interrogé après la construction de l’object et les mesures appropriées sockets.
  2. N’utilisez pas de constructeur (ou du moins ne construisez que ce qui ne peut pas échouer dans le constructeur, le cas échéant), puis utilisez une méthode init() pour effectuer (ou compléter) la construction. La méthode membre peut alors renvoyer une erreur en cas d’échec.

L’utilisation de la technique init() est généralement favorisée car elle peut être chaînée et mise à l’échelle mieux que le code “erreur” interne.

Encore une fois, ce sont des techniques qui proviennent d’environnements où des exceptions n’existent pas (telles que C). L’utilisation d’un langage tel que C ++ sans exception limite sa facilité d’utilisation et l’utilité de l’étendue de la bibliothèque standard.

N’essayant pas de répondre pleinement aux questions que vous avez posées, je vais simplement donner à Google un exemple de base de code qui n’utilise pas les exceptions comme mécanisme de gestion des erreurs.

Dans la base de code C ++ de Google, toutes les fonctions susceptibles d’échouer renvoient un object d’ status doté de méthodes telles que ok pour spécifier le résultat de l’appelé.
Ils ont configuré GCC pour échouer la compilation si le développeur a ignoré l’object d’ status retour.

De plus, à partir du petit code open source qu’ils fournissent (comme la librairie LevelDB), il semble qu’ils n’utilisent pas beaucoup la STL, de sorte que la gestion des exceptions devient rare. Comme le dit Titus Winters dans ses conférences à CPPC, ils “respectent la norme, mais ne l’identifient pas”.

Je pense que c’est une question d’attitude. Vous devez être dans le camp de “Je me fous si quelque chose échoue”. Cela se traduit généralement par du code, pour lequel il faut un débogueur (sur le site client) pour savoir pourquoi, soudain, quelque chose ne fonctionne plus. De plus, les personnes potentiellement impliquées dans l’ingénierie logicielle n’utilisent pas de code très complexe. Par exemple, il serait impossible d’écrire du code, qui repose sur le fait qu’il n’est exécuté que si toutes les n ressources sur lesquelles il repose ont été allouées avec succès (en utilisant RAII pour ces ressources). Ainsi: un tel codage entraînerait soit:

  • une quantité de code ingérable pour la gestion des erreurs
  • une quantité de code ingérable pour éviter l’exécution de code, qui repose sur l’allocation réussie de certaines ressources
  • pas de gestion des erreurs et donc une quantité considérable de support et de temps de développement

Notez que je parle de code moderne, en chargeant des DLL fournies par le client à la demande et en utilisant des processus enfants. Il existe de nombreuses interfaces sur lesquelles quelque chose peut échouer. Je ne parle pas de remplacement pour grep / more / ls / find.