Lancer des exceptions dans les DLL de jeu C ++? Avantages et inconvénients

8

Quels sont les avantages et les inconvénients de l'utilisation des exceptions en C ++ par rapport au développement de jeux.

Le guide de style Google indique qu'ils n'utilisent pas d'exceptions pour diverses raisons. Les mêmes raisons sont-elles pertinentes pour le développement de jeux?

Nous n'utilisons pas d'exceptions C ++ ... - google-styleguide.googlecode.com

Quelques questions auxquelles réfléchir.

  • Comment cela se rapporte au développement d'une bibliothèque utilisée à travers plusieurs projets.
  • Comment cela affecte-t-il les tests unitaires, les tests d'intégration, etc.?
  • Qu'est-ce que cela fait d'utiliser des bibliothèques tierces.
David Young
la source
3
Curieusement, personne n'avait d'avantages
David Young

Réponses:

7

L'utilisation d'exceptions en C ++ a également des conséquences désagréables si vous ne connaissez pas tous les détails de leur fonctionnement. Étant donné que de nombreux jeux sont écrits en C ++, cela peut être un facteur.

Par exemple, en C ++, lancer une exception dans un destructeur peut avoir de graves conséquences. Cela peut provoquer toutes sortes de comportements indéfinis et se bloquer, car vous pouvez vous retrouver piégé dans une situation où vous avez plus d'une exception active en vol. (Voir l'article n ° 8 de C ++ efficace pour plus d'informations à ce sujet.)

Bob Somers
la source
4
+1 pour C ++ efficace. Lecture de la 3e édition en ce moment même. Les exceptions me conviennent, tant que vous êtes très strict sur l'endroit où vous les utilisez, où vous les attrapez et vous pouvez garantir que tout code tiers fait de même.
Anthony
9

Joel sur les vues du logiciel sur les exceptions.

Vue intéressante qui ne figure actuellement dans aucune des réponses,

Le raisonnement est que je considère que les exceptions ne valent pas mieux que les «goto», considérées comme nuisibles depuis les années 1960, en ce qu'elles créent un saut brutal d'un point de code à un autre. En fait, ils sont bien pires que ceux de goto:

  1. Ils sont invisibles dans le code source. En regardant un bloc de code, y compris des fonctions qui peuvent ou non lever des exceptions, il n'y a aucun moyen de voir quelles exceptions peuvent être levées et d'où. Cela signifie que même une inspection minutieuse du code ne révèle pas de bogues potentiels.

  2. Ils créent trop de points de sortie possibles pour une fonction. Pour écrire du code correct, vous devez vraiment penser à chaque chemin de code possible à travers votre fonction. Chaque fois que vous appelez une fonction qui peut déclencher une exception et ne la détecte pas immédiatement, vous créez des opportunités de bugs surprise causés par des fonctions qui se sont arrêtées brutalement, laissant les données dans un état incohérent ou d'autres chemins de code que vous n'avez pas Penser à.

http://www.joelonsoftware.com/items/2003/10/13.html

David Young
la source
Halleluja ... Grande référence. J'ai eu une aversion avec des exceptions pour exactement cette raison, mais apparemment c'est la façon préférée de faire les choses de nos jours. Super de voir un bel article qui donne une autre vue
Toad
1
+1 parce que les exceptions ont un risque beaucoup trop élevé d'échouer en retard, c'est-à-dire lorsque le jeu est déjà livré.
martinpi
Je suis entièrement d'accord avec le point numéro 2. Je ne lutterai pas contre les langues qui utilisent des exceptions tout au long, mais je ne les considère pas non plus comme la «bonne» façon de gérer les conditions d'erreur.
Kylotan
5

Les exceptions ne sont pas prises en charge et sont donc fortement déconseillées dans au moins un environnement de développement de console moderne.

La plupart des développeurs de consoles que je connais préfèrent ne pas les utiliser de toute façon en raison de la surcharge supplémentaire dans l'exécutable et de la philosophie selon laquelle ils ne sont tout simplement pas nécessaires. RTTI est considéré de la même manière.

Dan Olson
la source
Sur quelle console n'est-il pas pris en charge?
David Young
J'essaie d'être timide à cause des NDA, même si cela a été révélé plus spécifiquement sur SO auparavant.
Dan Olson
2

De tous les projets sur lesquels j'ai travaillé dans les jeux, aucun n'a utilisé d'exceptions. La surcharge des appels de fonction est la principale raison. Comme mentionné précédemment, comme RTTI, ce n'est pas, dans la plupart des studios, un sujet de discussion. Pas parce que la plupart des codeurs viennent d'un milieu Java / universitaire, car franchement, la plupart n'en ont pas.
Cela n'affecte pas vraiment les tests unitaires comme vous l'affirmez simplement sur les conditions. Pour les bibliothèques, vous êtes mieux sans exception car vous l'imposerez à tous ceux qui l'utilisent.
Bien que cela rend certaines situations légèrement plus difficiles à gérer, cela vous oblige (imo) à avoir une configuration plus contrôlée, car les exceptions peuvent conduire à des abus. La tolérance aux erreurs est agréable, mais pas si elle conduit à un codage bâclé.
Attention, cela vient de quelqu'un qui n'a jamais utilisé la gestion des exceptions (enfin d'accord, une ou deux fois dans les outils - ça ne l'a pas fait pour moi, je suppose que c'est ce avec quoi on grandit).

Kaj
la source
1
Vous avez raison, mais malheureusement, plutôt que d'utiliser une gestion alternative des erreurs, la plupart des programmeurs de jeux reviennent simplement à planter ou à retourner NULL (ce qui entraînera probablement un crash de toute façon). Nous n'avons pas trouvé de remplacement décent.
tenpn
Renvoyer NULL, ou un code d'erreur, est un remplacement parfaitement raisonnable tant que vous vérifiez également les codes de retour.
JasonD
Oui, mais la plupart des programmeurs de jeux ne le font pas. C'est mon point.
tenpn
1
C'est un problème du langage C ++ autant que n'importe quoi - si vous voyez une ligne de code qui dit que function();vous ne savez pas instantanément si un code d'erreur est ignoré ou non. C'est probablement pourquoi les langues qui vous permettent d'ignorer une valeur de retour essaient de vous contraindre à préférer les exceptions.
Kylotan
1
L'application des arguments d'erreur n'est plus un problème. Le code moderne qui n'utilise pas d'exceptions s'appuie sur une Errorclasse qui est légère et si elle est ignorée, une assertion est déclenchée. Il ne peut donc pas être ignoré, pas plus qu'une exception ne peut être ignorée.
Samaursa
1

La chose la plus importante pour moi en tant que développeur de jeux professionnel est que les exceptions doivent être écrites par des personnes qui comprennent le fonctionnement des exceptions (qui sont peu nombreuses dans l'industrie des jeux) et également le code sous-jacent qui les exécute (qui est un désordre ramifié de toute façon et tuera votre processeur en ordre) devait être écrit par des personnes fournissant votre compilateur / éditeur de liens. Le problème est qu'ils ne disposent que d'une quantité limitée de ressources, ils se concentrent donc sur l'écriture de meilleures optimisations et correctifs pour le code que les développeurs de jeux utilisent ... ce qui n'est généralement pas une exception. Si vous avez un bug d'exception sur du matériel moderne avec de nouveaux compilateurs, qui allez-vous appeler? votre expert d'exception qui pense que tout va bien avec le code, ou le vendeur qui dit, oui, bientôt, nous '

Il n'y a rien de fondamentalement mauvais avec les exceptions, mais elles ne sont pas bonnes parce que la réalité fait obstacle à la théorie.

Richard Fabian
la source
0

Pour moi, la gestion des exceptions peut être formidable si tout est conforme à RAII et que vous réservez des exceptions pour des chemins vraiment exceptionnels (ex: un fichier corrompu en cours de lecture) et que vous n'avez jamais besoin de dépasser les limites du module et que toute votre équipe est à leurs côtés ......

EH zéro coût

De nos jours, la plupart des compilateurs mettent en œuvre une gestion des exceptions à coût nul, ce qui peut le rendre encore moins cher que la ramification manuelle sur des conditions d'erreur dans les chemins d'exécution normaux, bien qu'en échange, cela rend les chemins exceptionnels extrêmement coûteux (il y a aussi un peu de code, mais probablement pas plus que si vous avez traité à fond toutes les erreurs possibles manuellement). Le fait que lancer soit extrêmement coûteux ne devrait pas avoir d'importance si les exceptions sont utilisées de la manière prévue en C ++ (pour des circonstances vraiment exceptionnelles qui ne devraient pas se produire dans des conditions normales).

Inversion des effets secondaires

Cela dit, rendre tout conforme au RAII est beaucoup plus facile à dire qu'à faire. Il est facile pour les ressources locales stockées dans une fonction de les encapsuler dans des objets C ++ avec des destructeurs qui se nettoient, mais il n'est pas si facile d'écrire une logique d'annulation / restauration pour chaque effet secondaire qui pourrait se produire dans l'ensemble du logiciel. Considérez simplement combien il est difficile d'écrire un conteneur comme celui std::vectorqui est la séquence d'accès aléatoire la plus simple pour être parfaitement protégé contre les exceptions. Multipliez maintenant cette difficulté à travers toutes les structures de données d'un logiciel à grande échelle.

À titre d'exemple de base, considérons une exception rencontrée lors du processus d'insertion d'éléments dans un graphique de scène. Pour récupérer correctement de cette exception, vous devrez peut-être annuler ces modifications dans le graphique de la scène et restaurer le système dans un état comme si l'opération ne s'était jamais produite. Cela signifie supprimer automatiquement les enfants insérés dans le graphique de la scène lors de la rencontre d'une exception, peut-être d'un garde de portée.

Faire cela à fond dans un logiciel qui provoque de nombreux effets secondaires et traite avec beaucoup d'état persistant est beaucoup plus facile à dire qu'à faire. Souvent, je pense que la solution pragmatique est de ne pas se soucier de cela dans l'intérêt de faire expédier les choses. Avec le bon type de base de code qui a une sorte de système d'annulation central et peut-être des structures de données persistantes et le nombre minimal de fonctions provoquant des effets secondaires, vous pourriez être en mesure d'obtenir une sécurité d'exception à tous les niveaux ... mais c'est beaucoup de des trucs dont vous avez besoin si tout ce que vous allez faire est d'essayer de rendre votre code protégé contre les exceptions partout.

Il y a quelque chose de mal là-dedans, car l'inversion conceptuelle des effets secondaires est un problème difficile, mais elle est rendue plus difficile en C ++. Il est en fait parfois plus facile d'inverser les effets secondaires en C en réponse à un code d'erreur que de le faire en réponse à une exception en C ++. L'une des raisons est que tout point donné d'une fonction pourrait potentiellement throwen C ++. Si vous essayez d'écrire un conteneur générique fonctionnant avec type T, vous ne pouvez même pas anticiper où se trouvent ces points de sortie, car tout ce qui Tpourrait impliquer pourrait être lancé. Même en comparant les uns Taux autres pour l'égalité pourrait jeter. Votre code doit gérer toutes les possibilités , et le nombre de possibilités se multiplie exponentiellement en C ++ alors qu'en C, elles sont beaucoup moins nombreuses.

Absence de normes

Un autre problème de gestion des exceptions est le manque de normes centrales. Il y en a comme j'espère que tout le monde convient que les destructeurs ne devraient jamais lancer car les rollbacks ne devraient jamais échouer, mais cela ne fait que couvrir un cas pathologique qui ferait généralement planter le logiciel si vous violiez la règle.

Il devrait y avoir des normes plus sensées comme ne jamais lancer depuis un opérateur de comparaison (toutes les fonctions qui sont logiquement immuables ne devraient jamais lancer), ne jamais lancer depuis un ctor de déplacement, etc. De telles garanties faciliteraient tellement l'écriture de code sans exception mais nous n'avons pas de telles garanties. Nous devons en quelque sorte respecter la règle selon laquelle tout peut être jeté - sinon maintenant, peut-être à l'avenir.

Pire encore, des personnes d'horizons linguistiques différents voient les exceptions très différemment. En Python, ils ont en fait cette philosophie de "sauter avant de regarder" qui implique de déclencher des exceptions dans les chemins d'exécution normaux! Lorsque vous obtenez alors de tels développeurs écrivant du code C ++, vous pourriez regarder des exceptions levées à gauche et à droite pour des choses qui ne sont pas vraiment exceptionnelles, et gérer tout cela peut être un cauchemar si vous essayez d'écrire du code générique, par exemple

La gestion des erreurs

La gestion des erreurs présente l'inconvénient que vous devez vérifier chaque erreur possible qui pourrait se produire. Mais cela a un avantage: parfois, vous pouvez vérifier les erreurs un peu tard avec le recul, comme glGetErrorréduire considérablement les points de sortie d'une fonction et les rendre explicites. Parfois, vous pouvez continuer à exécuter le code un peu plus longtemps avant de vérifier et de propager et de récupérer après une erreur, et parfois cela peut être vraiment plus facile que la gestion des exceptions (en particulier avec l'inversion des effets secondaires). Mais vous pourriez même ne pas trop vous soucier des erreurs dans un jeu, peut-être simplement arrêter le jeu avec un message si vous rencontrez des choses comme des fichiers corrompus, des erreurs de mémoire insuffisante, etc.

Comment cela se rapporte au développement d'une bibliothèque utilisée à travers plusieurs projets.

Une partie désagréable des exceptions dans les contextes DLL est que vous ne pouvez pas jeter en toute sécurité des exceptions d'un module à un autre à moins que vous ne puissiez garantir qu'elles sont générées par le même compilateur, utiliser les mêmes paramètres, etc. Donc, si vous écrivez comme une architecture de plugin destiné aux utilisateurs de toutes sortes de compilateurs et peut-être même de différents langages comme Lua, C, C #, Java, etc., les exceptions commencent souvent à devenir une nuisance majeure car vous devez avaler chaque exception et les traduire en erreur codes de toute façon partout.

Qu'est-ce que cela fait d'utiliser des bibliothèques tierces.

S'ils sont des dylibs, ils ne peuvent pas lancer d'exceptions en toute sécurité pour les raisons mentionnées ci-dessus. Ils devraient utiliser des codes d'erreur, ce qui signifie également que vous, en utilisant la bibliothèque, devriez constamment vérifier les codes d'erreur. Ils pourraient envelopper leur dylib avec une bibliothèque d'encapsulation C ++ liée statiquement que vous construisez vous-même, qui traduit les codes d'erreur du dylib en exceptions levées (essentiellement en jetant de votre binaire dans votre binaire). En général, je pense que la plupart des bibliothèques tierces ne devraient même pas déranger et se contenter de codes d'erreur si elles utilisent des bibliothèques dylibs / partagées.

Comment cela affecte-t-il les tests unitaires, les tests d'intégration, etc.?

Je ne rencontre généralement pas autant d'équipes testant des chemins exceptionnels / d'erreur de leur code. J'avais l'habitude de le faire et j'avais en fait du code qui pouvait récupérer correctement bad_allocparce que je voulais rendre mon code ultra-robuste, mais cela n'aide pas vraiment quand je suis le seul à le faire dans une équipe. À la fin, j'ai cessé de me déranger. J'imagine un logiciel essentiel à la mission que des équipes entières pourraient vérifier pour s'assurer que leur code récupère solidement des exceptions et des erreurs dans tous les cas possibles, mais c'est beaucoup de temps à investir. Le temps a du sens dans les logiciels critiques, mais peut-être pas dans un jeu.

Amour-haine

Les exceptions sont également mon type de fonctionnalité d'amour / haine du C ++ - l'une des fonctionnalités les plus profondément impactantes du langage, ce qui fait que RAII ressemble plus à une exigence qu'à une commodité, car cela change la façon dont vous devez écrire du code à l'envers, par exemple , C pour gérer le fait qu'une ligne de code donnée pourrait être un point de sortie implicite pour une fonction. Parfois, je souhaite presque que la langue n'ait pas d'exceptions. Mais il y a des moments où je trouve les exceptions vraiment utiles, mais elles sont trop d'un facteur dominant pour moi si je choisis d'écrire un morceau de code en C ou C ++.

Ils seraient exponentiellement plus utiles pour moi si le comité de normalisation se concentrait vraiment sur l'établissement de normes ABI qui permettaient de diffuser des exceptions en toute sécurité entre les modules, au moins du code C ++ au code C ++. Ce serait génial si vous pouviez lancer des langues en toute sécurité, bien que ce soit un rêve de pipe. L'autre chose qui rendrait les exceptions exponentiellement plus utiles pour moi est que les noexceptfonctions provoquent réellement une erreur de compilation si elles tentent d'appeler une fonctionnalité qui pourrait être lancée au lieu d'appeler simplement std::terminateà rencontrer une exception. C'est presque inutile (pourrait aussi bien appeler cela à la icantexceptplace de noexcept). Il serait tellement plus facile de garantir que tous les destructeurs sont protégés contre les exceptions, par exemple, sinoexceptprovoquait en fait une erreur du compilateur si le destructeur faisait tout ce qui pouvait être lancé. Si cela coûte trop cher pour être complètement déterminé au moment de la compilation, alors faites en sorte que les noexceptfonctions (que tous les destructeurs implicitement devraient être) ne soient autorisées à appeler de la même manière que les noexceptfonctions / opérateurs, et en faire une erreur du compilateur à lancer à partir de l'un d'entre eux.


la source
-2

Pour autant que je le vois, il y a deux raisons principales pour lesquelles il n'est pas traditionnellement utilisé dans le développement de jeux:

  • La plupart des programmeurs de jeux sont soit des diplômés, qui ont appris sur Java et Haskell, soit des programmeurs C, qui n'ont aucune expérience des exceptions. Ils ont donc très peur d'eux et ne savent pas comment les utiliser correctement.
  • Le déroulement de la pile lorsqu'une exception est levée a des implications sur les performances (bien que ce soit une sorte de sagesse reçue, et certaines références seraient excellentes).
tenpn
la source
3
Les programmeurs Java ne savent pas comment utiliser les exceptions? Java est basé sur la gestion des exceptions. Même les programmeurs Java les plus élémentaires comprennent les blocs Try, Catch, Enfin. Vous ne pouvez vraiment pas programmer en Java sans lancer d'exceptions ou les gérer.
David Young
4
C'est plus que simplement dérouler la pile. Les exceptions ajoutent une surcharge à chaque appel de fonction. codeproject.com/KB/cpp/exceptionhandler.aspx
Jonathan Fischoff
@David Young Je parle davantage des garanties fortes / faibles, et du côté plus complexe de l'utilisation des exceptions, dans lequel les diplômés universitaires ne sont peut-être pas expérimentés. mettre moins l'accent sur la façon d'utiliser correctement les exceptions Java. Mais tu as raison, je n'étais pas clair.
tenpn
En outre, «peur» n'est peut-être pas le bon mot. :)
tenpn
3
Je dirais qu'ils sont souvent l'opposé de "peur" - si vous n'avez pas de règle stricte "pas d'exceptions, pas d'exceptions", les diplômés Java les plus récents finiront par lancer des exceptions plus souvent que de revenir, tout comme les programmeurs C novices utilisez goto et break plutôt que d'écrire de bons invariants de boucle.