Comme d'autres l'ont souligné, assert
c'est en quelque sorte votre dernier bastion de défense contre les erreurs de programmation qui ne devraient jamais se produire. Ce sont des contrôles de santé mentale qui, espérons-le, ne devraient pas échouer à gauche et à droite au moment de l'expédition.
Il est également conçu pour être omis des versions stables, pour toutes les raisons que les développeurs pourraient trouver utiles: l'esthétique, les performances, tout ce qu'ils veulent. Cela fait partie de ce qui sépare une version de débogage d'une version de version, et par définition une version de version est dépourvue de telles affirmations. Il y a donc une subversion de la conception si vous voulez libérer la "version de version avec des assertions en place" analogique qui serait une tentative de version avec une _DEBUG
définition de préprocesseur et aucune NDEBUG
définition; ce n'est plus vraiment une version.
La conception s'étend même dans la bibliothèque standard. A titre d'exemple très basique parmi les nombreux, beaucoup d'implémentations de std::vector::operator[]
sera assert
une vérification de bon sens pour vous assurer que vous n'êtes pas de vérifier le vecteur hors limites. Et la bibliothèque standard commencera à fonctionner beaucoup, bien pire si vous activez ces vérifications dans une version. Une référence d' vector
utilisationoperator[]
et un ctor de remplissage avec de telles assertions incluses par rapport à un ancien tableau dynamique simple montrera souvent que le tableau dynamique est considérablement plus rapide jusqu'à ce que vous désactiviez de telles vérifications, de sorte qu'elles ont souvent un impact sur les performances de manière très, loin d'être triviale. Une vérification de pointeur nul ici et une vérification hors limites peuvent en fait devenir une dépense énorme si ces vérifications sont appliquées des millions de fois sur chaque trame dans les boucles critiques précédant le code aussi simple que de déréférencer un pointeur intelligent ou d'accéder à un tableau.
Vous souhaitez donc probablement un outil différent pour le travail et un outil qui n'est pas conçu pour être omis des versions de version si vous souhaitez des versions de version qui effectuent de tels contrôles d'intégrité dans des domaines clés. Le plus utile que je trouve personnellement est la journalisation. Dans ce cas, lorsqu'un utilisateur signale un bogue, les choses deviennent beaucoup plus faciles s'il joignent un journal et la dernière ligne du journal me donne une grande idée de l'endroit où le bogue s'est produit et de ce qu'il pourrait être. Ensuite, en reproduisant leurs étapes dans une version de débogage, je pourrais également obtenir un échec d'assertion, et cet échec d'assertion me donne en outre d'énormes indices pour rationaliser mon temps. Pourtant, étant donné que la journalisation est relativement coûteuse, je ne l'utilise pas pour appliquer des contrôles d'intégrité extrêmement bas comme pour s'assurer qu'un tableau n'est pas accessible hors limites dans une structure de données générique.
Pourtant, enfin, et quelque peu en accord avec vous, je pourrais voir un cas raisonnable où vous pourriez réellement vouloir remettre aux testeurs quelque chose ressemblant à une version de débogage pendant les tests alpha, par exemple, avec un petit groupe de testeurs alpha qui, par exemple, ont signé un NDA . Là, cela pourrait rationaliser les tests alpha si vous remettez à vos testeurs autre chose qu'une version complète avec des informations de débogage attachées ainsi que des fonctionnalités de débogage / développement comme des tests qu'ils peuvent exécuter et une sortie plus détaillée pendant qu'ils exécutent le logiciel. J'ai au moins vu quelques grandes sociétés de jeu faire des choses comme ça pour alpha. Mais c'est pour quelque chose comme alpha ou des tests internes où vous essayez vraiment de donner aux testeurs autre chose qu'une version. Si vous essayez de livrer une version, alors par définition, elle ne devrait pas avoir_DEBUG
défini ou bien qui est vraiment confondre la différence entre une version "debug" et "release".
Pourquoi ce code doit-il être supprimé avant sa sortie? Les vérifications ne sont pas vraiment une perte de performance et si elles échouent, il y a certainement un problème sur lequel je préférerais un message d'erreur plus direct.
Comme indiqué ci-dessus, les vérifications ne sont pas nécessairement triviales du point de vue des performances. Beaucoup sont probablement triviaux mais encore une fois, même la bibliothèque standard les utilise et cela pourrait affecter les performances de manière inacceptable pour de nombreuses personnes dans de nombreux cas si, disons, la traversée à accès aléatoire de std::vector
prenait 4 fois plus de temps dans ce qui est censé être une version optimisée en raison de sa vérification des limites qui n'est jamais censée échouer.
Dans une ancienne équipe, nous avons dû faire en sorte que notre bibliothèque de matrices et de vecteurs exclue certaines assertions dans certains chemins critiques juste pour accélérer les builds de débogage, car ces assertions ralentissaient les opérations mathématiques de plus d'un ordre de grandeur au point où elles se trouvaient. commencer à nous obliger à attendre 15 minutes avant que nous puissions même retracer dans le code d'intérêt. Mes collègues voulaient simplement supprimer leasserts
purement et simplement parce qu'ils ont constaté que le simple fait de faire cela faisait une énorme différence. Au lieu de cela, nous nous sommes contentés de faire en sorte que les chemins de débogage critiques les évitent. Lorsque nous avons fait en sorte que ces chemins critiques utilisent directement les données vectorielles / matricielles sans passer par la vérification des limites, le temps nécessaire pour effectuer l'opération complète (qui comprenait plus que des calculs vectoriels / matriciels) a été réduit de quelques minutes à quelques secondes. C'est donc un cas extrême, mais les affirmations ne sont certainement pas toujours négligeables du point de vue des performances, pas même de près.
Mais c'est aussi juste la façon dont asserts
sont conçus. S'ils n'avaient pas un impact énorme sur les performances à tous les niveaux, je pourrais le préférer s'ils étaient conçus comme plus qu'une fonctionnalité de création de débogage ou si nous pouvions utiliser vector::at
ce qui inclut la vérification des limites même dans les versions et les sorties hors limites accès, par exemple (mais avec un énorme succès de performance). Mais actuellement, je trouve leur conception beaucoup plus utile, étant donné leur énorme impact sur les performances dans mes cas, en tant que fonctionnalité de débogage uniquement qui est omise lorsqu'elle NDEBUG
est définie. Pour les cas avec lesquels j'ai travaillé au moins, cela fait une énorme différence pour une version de construction d'exclure les contrôles d'intégrité qui ne devraient jamais échouer en premier lieu.
vector::at
contre. vector::operator[]
Je pense que la distinction de ces deux méthodes est au cœur de cela ainsi que l'alternative: les exceptions. vector::operator[]
implémentations généralement assert
pour s'assurer que l'accès hors limites déclenchera une erreur facilement reproductible lorsque vous tenterez d'accéder à un vecteur hors limites. Mais les implémenteurs de bibliothèque le font en supposant que cela ne coûtera pas un centime dans une version optimisée.
Pendant ce temps, il vector::at
est fourni, qui effectue toujours les vérifications et les lancements hors limites, même dans les versions, mais il a une pénalité de performance au point où je vois souvent beaucoup plus de code vector::operator[]
que vector::at
. Une grande partie de la conception de C ++ fait écho à l'idée de «payer pour ce que vous utilisez / avez besoin», et beaucoup de gens préfèrent souvent operator[]
, ce qui ne dérange même pas avec les limites de l'archivage des versions, en se basant sur l'idée qu'elles ne pas besoin que les limites vérifient leurs versions optimisées. Soudain, si les assertions étaient activées dans les versions, les performances de ces deux seraient identiques et l'utilisation du vecteur finirait toujours par être plus lente qu'un tableau dynamique. Ainsi, une grande partie de la conception et des avantages des assertions est basée sur l'idée qu'elles deviennent gratuites dans une version.
release_assert
C'est intéressant après avoir découvert ces intentions. Naturellement, les cas d'utilisation de chacun seraient différents, mais je pense que je trouverais une utilisation pour un release_assert
qui fait la vérification et plantera le logiciel affichant un numéro de ligne et un message d'erreur même dans les versions.
Ce serait pour certains cas obscurs dans mon cas où je ne veux pas que le logiciel récupère gracieusement comme il le ferait si une exception était levée. Je voudrais qu'il se bloque même dans la version dans ces cas afin que l'utilisateur puisse recevoir un numéro de ligne pour signaler quand le logiciel a rencontré quelque chose qui ne devrait jamais se produire, toujours dans le domaine des vérifications d'intégrité pour les erreurs de programmation, pas les erreurs d'entrée externes comme exceptions, mais assez bon marché pour être fait sans se soucier de son coût de sortie.
Il y a en fait certains cas où je trouverais un crash dur avec un numéro de ligne et un message d'erreur préférable à une récupération gracieuse d'une exception levée qui pourrait être assez bon marché pour être conservée dans une version. Et il y a des cas où il est impossible de récupérer à partir d'une exception, comme une erreur rencontrée en essayant de récupérer à partir d'une exception existante. Là, je trouverais un ajustement parfait pour un release_assert(!"This should never, ever happen! The software failed to fail!");
et naturellement ce serait très bon marché car le contrôle serait effectué à l'intérieur d'un chemin exceptionnel en premier lieu et ne coûterait rien dans les chemins d'exécution normaux.