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.
c++
software-engineering
David Young
la source
la source
Réponses:
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.)
la source
Joel sur les vues du logiciel sur les exceptions.
Vue intéressante qui ne figure actuellement dans aucune des réponses,
http://www.joelonsoftware.com/items/2003/10/13.html
la source
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.
la source
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).
la source
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.Error
classe 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.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.
la source
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::vector
qui 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
throw
en C ++. Si vous essayez d'écrire un conteneur générique fonctionnant avec typeT
, vous ne pouvez même pas anticiper où se trouvent ces points de sortie, car tout ce quiT
pourrait impliquer pourrait être lancé. Même en comparant les unsT
aux 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
glGetError
ré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.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.
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.
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_alloc
parce 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
noexcept
fonctions provoquent réellement une erreur de compilation si elles tentent d'appeler une fonctionnalité qui pourrait être lancée au lieu d'appeler simplementstd::terminate
à rencontrer une exception. C'est presque inutile (pourrait aussi bien appeler cela à laicantexcept
place denoexcept
). Il serait tellement plus facile de garantir que tous les destructeurs sont protégés contre les exceptions, par exemple, sinoexcept
provoquait 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 lesnoexcept
fonctions (que tous les destructeurs implicitement devraient être) ne soient autorisées à appeler de la même manière que lesnoexcept
fonctions / opérateurs, et en faire une erreur du compilateur à lancer à partir de l'un d'entre eux.la source
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 source