Note: Je ne joue pas l'avocat du diable ou quelque chose comme ça ici - je suis juste vraiment curieux puisque je ne suis pas moi-même dans ce camp.
La plupart des types de la bibliothèque standard ont soit des fonctions de mutation qui peuvent lever des exceptions (par exemple si l'allocation de mémoire échoue) soit des fonctions non-mutantes qui peuvent lever des exceptions (par exemple des accesseurs indexés hors limites). En plus de cela, de nombreuses fonctions gratuites peuvent lever des exceptions (par exemple operator new
et dynamic_cast<T&>
).
Comment gérez-vous pratiquement dans ce contexte "nous n'utilisons pas d'exceptions"?
Essayez-vous jamais d'appeler une fonction qui peut lancer? (Je ne vois pas comment cela évoluerait, donc je suis très intéressé de savoir comment vous y parvenez si tel est le cas)
Êtes-vous d'accord avec le lancement de bibliothèque standard et vous traitez "nous n'utilisons pas d'exceptions" comme "nous n'avons jamais jeter exceptions de nos code et nous n'avons jamais = catch exceptions à d'autres code "?
Désactivez-vous complètement la gestion des exceptions via les commutateurs du compilateur? Dans l'affirmative, comment fonctionnent les parties génératrices d'exceptions de la bibliothèque standard?
EDIT Vos constructeurs peuvent-ils échouer, ou utilisez-vous par convention une construction en 2 étapes avec une fonction d'initialisation dédiée qui peut retourner un code d'erreur en cas d'échec ( que le constructeur ne peut pas), ou faites-vous autre chose?
MODIFIER Précision mineure 1 semaine après le début de la question ... Une grande partie du contenu des commentaires et des questions ci-dessous se concentre sur le pourquoi aspects des exceptions vs "autre chose". Je ne m'intéresse pas à cela, mais quand vous choisissez de faire "autre chose", comment traitez-vous avec les parties de bibliothèque standard qui faire lever des exceptions?
Je répondrai pour moi et mon coin du monde. J'écris c ++ 14 (il aura 17 ans une fois que les compilateurs auront un meilleur support) des applications financières critiques de latence qui traitent des sommes d'argent gigantesques et ne peuvent jamais baisser. L'ensemble de règles est le suivant:
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 soit trivialement copiables, donc les destructeurs sont presque absents (il y a quelques exceptions, comme les gardes de portée). Fondamentalement, nous faisons de la sécurité de type C + des modèles + des lambdas. Bien sûr, les exceptions sont désactivées via le commutateur du compilateur. Quant à la STL, les bonnes parties de celle-ci (à savoir: algorithme, numérique, type_traits, itérateur, atomique, ...) sont toutes utilisables. Les parties générant des exceptions coïncident avec les parties d'allocation de mémoire d'exécution et les parties semi-OO, nous pouvons donc nous débarrasser de toutes les failles en une seule fois: flux, conteneurs sauf std :: array, std :: string.
Pourquoi faire ceci?
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éfini 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 faire face au fait que les exceptions levées sont simplement abandonnées. Vous pouvez toujours capter le signal et imprimer la pile (il y a une bonne réponse sur SO qui explique cela), mais vous feriez mieux d'éviter ce genre de choses qui se produisent (au 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 demandez de ne pas utiliser d'exceptions, il y a une question plus générale sur la bibliothèque standard: Êtes-vous en utilisant la bibliothèque standard lorsque vous êtes dans l'un des "nous n'utilisons pas exceptions "camps?
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 pour 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 gèrent pas trop bien (ou même pas du tout). Pour un code de production de pointe AAA, les exceptions sont de toute façon trop lourdes, c'est donc un scénario gagnant-gagnant dans de tels cas.
En d'autres termes, pour de nombreux programmeurs, désactiver les exceptions va de pair avec ne pas utiliser du tout STL.
Remarque J'utilise des exceptions ... mais j'ai été forcé de ne pas le faire.
Essayez-vous de ne jamais appeler une fonction qui peut lancer? (Je ne vois pas comment cela évoluerait, donc je suis très intéressé de savoir comment vous y parvenez si tel est le cas)
Cela serait probablement irréalisable, du moins à grande échelle. De nombreuses fonctions peuvent atterrir, les éviter paralysent entièrement votre base de code.
Êtes-vous d'accord avec le lancement de bibliothèque standard et vous traitez "nous n'utilisons pas d'exceptions" comme "nous ne lançons jamais d'exceptions de notre code et nous n'attrapons jamais d'exceptions du code d'autrui"?
Vous devez à peu près être d'accord avec ça ... Si le code de bibliothèque va lever une exception et que votre code ne va pas la gérer, la terminaison est le comportement par défaut.
Désactivez-vous complètement la gestion des exceptions via les commutateurs du compilateur? Dans l'affirmative, comment fonctionnent les parties génératrices d'exceptions de la bibliothèque standard?
C'est possible (à l'époque, c'était parfois populaire pour certains types de projets); Les compilateurs prennent en charge/peuvent prendre en charge cette fonctionnalité, mais vous devrez consulter leur documentation pour savoir quels seraient et pourraient être les résultats (et quelles fonctionnalités linguistiques sont prises en charge dans ces conditions).
En général, lorsqu'une exception est levée, le programme doit abandonner ou quitter autrement. 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 d'exceptions"
La plupart des fonctions ont un ensemble de conditions préalables qui peuvent être vérifiées avant l'appel. Vérifiez-les. S'ils ne sont pas respectés, ne passez pas l'appel; revenir à ce que la gestion des erreurs est dans ce code. Pour ces 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 abandonné.
Vous pourriez chercher à éviter les bibliothèques qui lèvent des exceptions - vous l'avez demandé dans le contexte de la bibliothèque standard, donc cela ne fonctionne pas ' t tout à fait adapté au projet de loi, mais il reste une option.
Autres stratégies possibles; Je sais que cela semble banal, mais choisissez une langue qui ne les utilise pas. C pourrait bien faire ...
... au cœur de ma question (votre interaction avec la bibliothèque standard, le cas échéant), je suis très intéressé à entendre parler de vos constructeurs. Peuvent-ils échouer, ou utilisez-vous par convention une construction en 2 étapes avec une fonction d'initialisation dédiée qui peut retourner un code d'erreur en cas d'échec (ce que le constructeur ne peut pas)? Ou quelle est votre stratégie là-bas?
Si des constructeurs sont utilisés, deux approches sont généralement utilisées pour indiquer l'échec;
enum
pour indiquer l'échec et sa nature. Cela peut être interrogé après la construction de l'objet et les mesures appropriées prises.init()
quelconque pour faire (ou terminer) 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 privilégiée car elle peut être chaînée et évolue mieux que le code "erreur" interne.
Encore une fois, ce sont des techniques qui proviennent d'environnements où il n'y a pas d'exceptions (comme C). L'utilisation d'un langage tel que C++ sans exception limite son 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 pour traiter les erreurs.
Dans la base de code Google C++, toutes les fonctions qui peuvent échouer retournent un objet status
qui a des méthodes comme 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'objet return status
.
De plus, à partir du petit code open source qu'ils fournissent (comme la bibliothèque LevelDB), il semble qu'ils n'utilisent pas beaucoup STL de toute façon, donc la gestion des exceptions devient rare. comme le dit Titus Winters dans ses conférences à CPPCon, ils "respectent la norme, mais ne l'idolâtrent pas".
Je pense que c'est une question d'attitude. Vous devez être dans le camp de "Je m'en fiche si quelque chose échoue". Cela se traduit généralement par du code, pour lequel on a besoin d'un débogueur (sur le site du client) pour savoir pourquoi tout à coup quelque chose ne fonctionne plus. De même, potentiellement les personnes qui font de l '"ingénierie" logicielle de cette manière, n'utilisent pas de code très complexe. Par exemple. on serait incapable d'écrire du code, qui repose sur le fait qu'il n'est exécuté que si toutes les n ressources sur lesquelles il s'appuie ont été allouées avec succès (tout en utilisant RAII pour ces ressources). Ainsi: Un tel codage entraînerait soit:
Notez que je parle de code moderne, de chargement de DLL fournies par le client à la demande et d'utilisation de processus enfants. Il existe de nombreuses interfaces sur lesquelles quelque chose peut échouer. Je ne parle pas d'un remplacement pour grep/more/ls/find.