J'ai tendance à ajouter de nombreuses assertions à mon code C++ pour faciliter le débogage sans affecter les performances des versions. Maintenant, assert
est une macro C pure conçue sans les mécanismes C++ à l'esprit.
C++ d'autre part définit std::logic_error
, Qui est destiné à être lancé dans les cas où il y a une erreur dans la logique du programme (d'où le nom). Lancer une instance pourrait être l'alternative parfaite, plus C++ à assert
.
Le problème est que assert
et abort
terminent tous les deux le programme immédiatement sans appeler de destructeurs, sautant ainsi le nettoyage, tandis que lever une exception manuellement ajoute des coûts d'exécution inutiles. Une solution consiste à créer une propre macro d'assertion SAFE_ASSERT
, Qui fonctionne exactement comme l'homologue C, mais lève une exception en cas d'échec.
Je peux penser à trois opinions sur ce problème:
#define
S en C++ est tout aussi mauvais.NDEBUG
, cela ne se produira jamais dans une version de version. La capture n'est pas nécessaire et expose les détails d'implémentation du code interne à main()
.Y a-t-il une réponse définitive à ce problème? Une référence professionnelle?
Edited: Ignorer les destructeurs n'est bien sûr pas un comportement indéfini.
Les assertions sont tout à fait appropriées dans le code C++. Les exceptions et autres mécanismes de gestion des erreurs ne sont pas vraiment destinés à la même chose que les assertions.
La gestion des erreurs concerne les cas où il est possible de récupérer ou de signaler une erreur à l'utilisateur. Par exemple, s'il y a une erreur en essayant de lire un fichier d'entrée, vous pouvez vouloir faire quelque chose à ce sujet. Des erreurs peuvent résulter de bogues, mais elles peuvent aussi simplement être la sortie appropriée pour une entrée donnée.
Les affirmations concernent des choses comme vérifier que les exigences d'une API sont remplies lorsque l'API ne serait pas normalement vérifiée, ou pour vérifier des choses que le développeur pense être garanties par la construction. Par exemple, si un algorithme nécessite une entrée triée, vous ne vérifieriez pas normalement cela, mais vous pourriez avoir une assertion pour le vérifier afin que le débogage génère ce type de bogue. Une assertion doit toujours indiquer un programme qui ne fonctionne pas correctement.
Si vous écrivez un programme où un arrêt impur pourrait causer un problème, vous pouvez éviter les assertions. Un comportement indéfini strictement en termes de langage C++ ne constitue pas un tel problème ici, car frapper une assertion est probablement déjà le résultat d'un comportement indéfini, ou la violation d'une autre exigence qui pourrait empêcher un nettoyage de fonctionner correctement.
De plus, si vous implémentez des assertions en termes d'exception, elles pourraient potentiellement être interceptées et "gérées" même si cela contredit le but même de l'assertion.
Les assertions sont pour débogage. L'utilisateur de votre code livré ne doit jamais les voir. Si une assertion est frappée, votre code doit être corrigé.
Les exceptions sont pour circonstances exceptionnelles. Si l'on en rencontre, l'utilisateur ne pourra pas faire ce qu'il veut, mais pourra peut-être reprendre ailleurs.
La gestion des erreurs concerne le déroulement normal du programme. Par exemple, si vous demandez à l'utilisateur un nombre et obtenez quelque chose d'imparable, c'est normal, car la saisie de l'utilisateur n'est pas sous votre contrôle et vous devez toujours gérer toutes les situations possibles comme une évidence. (Par exemple, boucle jusqu'à ce que vous ayez une entrée valide, en disant "Désolé, essayez à nouveau" entre les deux.)
Ne pas exécuter les destructeurs en raison de l'aborting () n'est pas un comportement indéfini!
Si c'était le cas, alors ce serait un comportement indéfini d'appeler std::terminate()
aussi, et quel serait l'intérêt de le fournir?
assert()
est tout aussi utile en C++ qu'en C. Les assertions ne sont pas pour la gestion des erreurs, elles sont pour abandonner le programme immédiatement.
Les assertions peuvent être utilisées pour vérifier les invariants d'implémentation internes, comme l'état interne avant ou après l'exécution d'une méthode, etc. Dans ce cas, le mieux que vous puissiez faire est de rompre le plus tôt possible sans passer d'exception à l'utilisateur. Ce qui est vraiment bien avec les assertions (au moins sous Linux), c'est que le vidage de mémoire est généré à la suite de l'arrêt du processus et vous pouvez donc facilement étudier la trace de la pile et les variables. C'est beaucoup plus utile pour comprendre l'échec de la logique que le message d'exception.
À mon humble avis, les assertions servent à vérifier les conditions qui, si elles sont violées, rendent tout le reste insensé. Et donc vous ne pouvez pas vous remettre d'eux ou plutôt, récupérer n'est pas pertinent.
Je les regrouperais en 2 catégories:
probabilité flottante () {return -1.0; }
affirmer (probabilité ()> 0,0)
int x = 1;
affirmer (x> 0);
Ce sont deux exemples triviaux mais pas trop loin de la réalité. Par exemple, pensez aux algorithmes naïfs qui renvoient des indices négatifs à utiliser avec des vecteurs. Ou des programmes intégrés dans du matériel personnalisé. Ou plutôt parce que merde arrive.
Et s'il y a de telles erreurs de développement, vous ne devriez pas avoir confiance en tout mécanisme de récupération ou de gestion des erreurs implémenté. Il en va de même pour les erreurs matérielles.