Quelle importance accordez-vous à la sécurité des exceptions dans votre code C ++?


19

Chaque fois que je songe à sécuriser fortement mon code, je justifie de ne pas le faire car cela prendrait tellement de temps. Considérez cet extrait relativement simple:

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

Pour implémenter une garantie d'exception de base, je pourrais utiliser un pointeur de portée sur les newappels. Cela empêcherait les fuites de mémoire si l'un des appels levait une exception.

Cependant, disons que je veux implémenter une garantie d'exception forte. Au moins, j'aurais besoin d'implémenter un pointeur partagé pour mes conteneurs (je n'utilise pas Boost), un nothrow Entity::Swappour ajouter les composants atomiquement et une sorte d'idiome pour ajouter atomiquement à la fois à Vectoret Map. Non seulement leur mise en œuvre prendrait beaucoup de temps, mais elle serait coûteuse car elle implique beaucoup plus de copies que la solution d'exception non sûre.

En fin de compte, il me semble que le temps passé à faire tout cela ne serait pas justifié juste pour que la CreateEntityfonction a simple soit fortement protégée contre les exceptions. Je veux probablement juste que le jeu affiche une erreur et se ferme à ce point de toute façon.

Jusqu'où prenez-vous cela dans vos propres projets de jeu? Est-il généralement acceptable d'écrire du code d'exception non sûr pour un programme qui peut simplement se bloquer en cas d'exception?


21
Lorsque j'ai travaillé sur des projets C ++ dans le passé, nous avons essentiellement fait comme si les exceptions n'existaient pas.
Tetrad

2
Personnellement, j'aime les exceptions et j'aime souvent trouver comment faire en sorte que mon code offre de solides garanties. Si vous allez juste planter quand vous obtenez une exception ... peut-être ne vous embêtez pas avec ça.

7
J'utilise personnellement les exceptions pour gérer .. hm .. les «exceptions» et non les erreurs. Par exemple, si je sais qu'une fonction peut planter, je la laisse planter, mais si je sais qu'une fonction peut ne pas réussir à lire une archive, mais il y a une autre façon, je l'entoure d'un try, catch. et si c'est une exception "incapable de lire", je le gère simplement dans l'autre sens sans la lecture de l'archive.
Gustavo Maciel

Ce que Gtoknu a dit, vous devez être conscient des exceptions que toute bibliothèque externe peut lancer.
Peter Ølsted

Réponses:


26

Si vous allez utiliser des exceptions (ce qui ne veut pas dire que vous devriez le faire, il y a des avantages et des inconvénients à cette approche qui sont en dehors de la portée spécifique de cette question), vous devez écrire correctement du code sûr d'exception.

Un code qui n'utilise pas d'exceptions et qui n'est explicitement pas protégé contre les exceptions est meilleur que le code qui les utilise, mais met à moitié sa sécurité d'exception. Si vous avez ce dernier cas, vous avez toute une classe de bogues et d'échecs qui peuvent se produire lorsqu'une exception se produit et que vous ne pouvez probablement pas récupérer du tout, rendant l'un des avantages des exceptions entièrement nul et non avenu. Même si c'est une classe d'échec relativement rare, c'est toujours possible.

Cela ne veut pas dire que si vous utilisez des exceptions, tout le code doit fournir la garantie d'exception forte - juste que chaque morceau de code doit fournir une garantie quelconque (aucune, de base, forte) de sorte que dans la consommation de ce code vous savez quelles garanties le consommateur peut vous offrir. En règle générale, plus le composant en question est bas, plus la garantie doit être solide.

Si vous ne fournissez jamais une garantie solide ou que vous n'acceptez jamais vraiment les paradigmes de gestion des exceptions et tout le travail et les inconvénients supplémentaires que cela implique, vous n'obtiendrez pas non plus vraiment tous les avantages que cela implique et vous pourriez avoir une facilité le temps de tout simplement renoncer aux exceptions.


12
Cela vaut à lui seul un +1: "Un code qui n'utilise pas d'exceptions et qui n'est explicitement pas protégé contre les exceptions est meilleur que le code qui les utilise, mais à moitié assure sa sécurité des exceptions."
Maximus Minimus

14

Ma règle générale: si vous devez utiliser des exceptions, utilisez des exceptions. Si vous n'avez pas besoin d'utiliser d'exceptions, n'utilisez pas d'exceptions. Si vous ne savez pas si vous devez ou non utiliser des exceptions, vous n'avez pas besoin d'utiliser d'exceptions.

Les exceptions sont là pour être votre dernière ligne de défense absolue dans les applications critiques en permanence. Si vous écrivez un logiciel de contrôle de la circulation aérienne qui ne peut jamais échouer, utilisez des exceptions. Si vous écrivez un code de contrôle pour une centrale nucléaire, utilisez des exceptions.

En développant un jeu, en revanche, il vaut mieux suivre le mantra: planter tôt, planter fort. S'il y a un problème, vous voulez vraiment le savoir le plus tôt possible; vous ne voulez jamais que les erreurs soient "traitées" de manière invisible, car cela masquera souvent la cause réelle des mauvais comportements ultérieurs et rendra le débogage beaucoup plus difficile qu'il ne devrait l'être.


5

Le problème des exceptions est que vous devez de toute façon y faire face. Si vous obtenez une exception en raison d'une mémoire insuffisante, il est possible que vous ne puissiez pas gérer cette erreur de toute façon. Pourrait aussi bien vider le jeu entier dans 1 gestionnaire d'exception géant qui affiche une boîte d'erreur.

Pour le développement de jeux, je préfère essayer de trouver des moyens de faire en sorte que mon code continue gracieusement avec les échecs lorsque cela est possible.

Par exemple, je coderai en dur un maillage spécial ERREUR dans mon jeu. C'est un cercle rouge tournant avec un X blanc dessus et une bordure blanche. Selon le jeu, un invisible peut être meilleur. Il a une instance singleton initialisée au démarrage. Comme il est intégré dans le code plutôt que d'utiliser des fichiers externes, il ne devrait pas être possible d'échouer.

Dans le cas où un appel normal à loadMesh échoue au lieu de renvoyer une erreur et de se bloquer sur le bureau, il enregistrera silencieusement l'échec dans la console / le fichier journal et renverra le maillage d'erreur codé en dur. J'ai découvert que Skyrim fait en fait la même chose quand un mod a foiré les choses. Il y a une bonne possibilité que le joueur ne voie même pas le maillage d'erreur (à moins qu'il y ait un problème majeur et que beaucoup d'entre eux le soient).

Il y a aussi un shader de secours. Celui qui fait les opérations de base de la matrice des sommets mais s'il n'y a pas de matrice uniforme pour une raison quelconque, il devrait toujours faire la vue orthogonale. Il n'a pas d'ombrage / éclairage / matériaux appropriés (bien qu'il ait un indicateur color_overide afin que je puisse l'utiliser avec mon maillage d'erreur).

Le seul problème possible est de savoir si la version du maillage d'erreur peut interrompre définitivement le jeu d'une manière ou d'une autre. Par exemple, assurez-vous qu'il n'applique pas la physique, car si le joueur charge une zone, le maillage d'erreur peut tomber au sol, puis s'il recharge cette fois le jeu avec le bon maillage, s'il est plus grand, il pourrait être encastré dans le sol. Cela ne devrait pas être trop difficile, car vous allez probablement stocker vos maillages physiques avec vos maillages graphiques. Les scripts peuvent également être un problème. Avec quelques trucs, vous devrez simplement mourir dur. Je ne sais pas vraiment à quoi servirait un `` niveau '' de repli (bien que vous puissiez faire une petite pièce avec un panneau indiquant qu'il y a une erreur, assurez-vous simplement que la sauvegarde est désactivée car vous ne voulez pas que le joueur y soit coincé).

Dans le cas de «nouveau», pour le développement de jeux, il pourrait être préférable d'envisager d'utiliser une sorte d'usine. Il peut vous donner divers autres avantages comme la pré-allocation d'objets avant d'en avoir réellement besoin et / ou de les créer en vrac. Cela facilite également la création de choses comme un index global d'objets que vous pouvez utiliser pour la gestion de la mémoire, en trouvant des choses par un identifiant (bien que vous puissiez également le faire dans les constructeurs). Et bien sûr, vous pouvez également gérer les erreurs d'allocation.


2

La grande chose à propos des contrôles d'exécution manquants et des plantages est la possibilité pour un pirate d'utiliser un crash pour reprendre le processus et peut-être par la suite la machine.

Maintenant, un pirate ne peut pas exploiter un crash réel, dès que le crash se produit, le processus est inutile et inoffensif. Si vous lisez simplement à partir d'un pointeur nul, c'est un crash net, vous faites une erreur et le processus meurt instantanément.

Le danger survient lorsque vous exécutez une commande qui n'est pas techniquement illégale, mais qui n'était pas ce que vous aviez l'intention de faire, alors un pirate pourrait être en mesure de diriger cette action en utilisant des données soigneusement conçues qui devraient autrement être sûres.

Une exception non capturée n'est pas en fait un véritable crash, c'est juste un moyen de terminer le programme. Les exceptions se produisent uniquement parce que quelqu'un a écrit une bibliothèque qui lève spécifiquement une exception dans certaines circonstances. Généralement pour éviter d'entrer dans une situation inattendue, qui pourrait être une ouverture pour un pirate.

En fait, la décision dangereuse est d'attraper des exceptions plutôt que de les laisser glisser, ce qui ne veut pas dire que vous ne devriez jamais le faire, mais assurez-vous que vous n'attrapez que celles que vous savez que vous pouvez manipuler et laissez le reste glisser. Sinon, vous risquez d'annuler la mesure de sécurité soigneusement mise dans votre bibliothèque.

Concentrez-vous sur les erreurs qui ne génèrent pas nécessairement des exceptions, comme l'écriture au-delà de la fin d'un tableau. Votre programme ne serait pas par hasard en mesure de le faire?


2

Vous devez examiner vos exceptions et examiner les conditions qui peuvent les déclencher. Beaucoup de temps, un grand pourcentage de ce que les gens utilisent pour les exceptions peut être évité par des vérifications appropriées pendant le cycle de développement / de mise au point / de test. Utilisez des assertions, passez des références, initialisez toujours les variables locales lors de la déclaration, mettez à zéro toutes les allocations, NULL après la libération, etc., et un grand nombre de cas d'exceptions théoriques disparaissent.

Considérez: si quelque chose échoue et peut être un candidat pour lever une exception - quel est l'impact? Quelle importance cela at-il? Si vous échouez à une allocation de mémoire dans un jeu (pour prendre votre exemple d'origine), vous avez généralement un problème plus important à résoudre que la façon dont vous gérez le cas d'échec, et la gestion du cas d'échec au moyen d'une exception ne fera pas ce problème plus gros allez-vous en. Pire - cela peut en fait masquer le fait que le plus gros problème est toujours là. Veux-tu çà? Je sais que non. Je sais que si j'échoue une allocation de mémoire, je veux planter et graver horriblement et le faire aussi près que possible du point d'échec, afin de pouvoir pénétrer dans le débogueur, comprendre ce qui se passe et le corriger correctement.


1

Je ne sais pas si citer quelqu'un d'autre est approprié sur SE, mais je pense qu'aucune des autres réponses n'a vraiment abordé ce sujet.

Citant Jason Gregory dans son livre Game Engine Architecture . (GRAND LIVRE!)

"La gestion structurée des exceptions (SEH) ajoute beaucoup de surcharge au programme. Chaque trame de pile doit être augmentée pour contenir les informations supplémentaires requises par le processus de déroulement de la pile. De plus, le déroulement de la pile est généralement très lent - de l'ordre de deux à trois fois plus cher que le simple retour de la fonction. En outre, si une seule fonction de votre programme (ou bibliothèque) utilise SEH, votre programme entier doit utiliser SEH. Le compilateur ne peut pas savoir quelles fonctions pourraient être au-dessus de vous sur la pile des appels lorsque vous jetez une exception. "

Cela étant dit, mon moteur que je construis n'utilise pas d'exceptions. Cela est dû au coût de performance potentiel mentionné ci-dessus et au fait que, à toutes fins utiles, les codes de retour d'erreur fonctionnent correctement. La seule fois où j'ai vraiment besoin de rechercher les échecs est l'initialisation et le chargement des ressources et dans ces cas, le retour d'un booléen indiquant le succès fonctionne correctement.


2
La gestion structurée des exceptions est un type spécifique de gestion des exceptions disponible sur Windows, qui n'est pas le même que les exceptions C ++ en général.
Kylotan

Doh, je ne peux pas croire que je ne savais pas cela. Merci pour la correction!
KlashnikovKid

0

Je sécurise toujours mon exception de code, simplement parce qu'elle permet de savoir plus facilement où se posent les problèmes, car je peux enregistrer quelque chose chaque fois qu'une exception est générée ou interceptée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.