J'ai tendance à ajouter beaucoup d'assertions à mon code C ++ pour faciliter le débogage sans affecter les performances des versions de version. Maintenant, assert
est une macro C pure conçue sans mécanismes C ++ à l'esprit.
C ++ d'autre part définit std::logic_error
, qui est censé être levé dans les cas où il y a une erreur dans la logique du programme (d'où le nom). Lancer une instance pourrait bien être l'alternative parfaite, plus C ++ assert
.
Le problème est que , assert
et à la abort
fois mettre fin au programme immédiatement sans appeler Destructeurs, sauter donc le nettoyage, alors lancer une exception ajoute manuellement les coûts d'exécution inutiles. Une façon de contourner cela serait de 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:
- Tenez-vous en à l'affirmation de C. Étant donné que le programme se termine immédiatement, peu importe que les modifications soient correctement déroulées. De plus, l'utilisation de
#define
s en C ++ est tout aussi mauvaise. - Lancez une exception et attrapez-la dans main () . Autoriser le code à ignorer les destructeurs dans n'importe quel état du programme est une mauvaise pratique et doit être évité à tout prix, tout comme les appels à terminate (). Si des exceptions sont lancées, elles doivent être interceptées.
- Lancez une exception et laissez-le terminer le programme. Une exception mettant fin à un programme est acceptable, et à cause de
NDEBUG
cela, cela ne se produira jamais dans une version de version. La capture est inutile 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?
Modifié: sauter les destructeurs n'est, bien sûr, pas un comportement indéfini.
la source
logic_error
c'est l'erreur logique. Une erreur dans la logique du programme est appelée un bogue. Vous ne résolvez pas les bogues en lançant des exceptions.static_assert
là où c'est approprié si vous en avez à disposition.std::bug
?std::abort()
; il déclenchera simplement un signal qui entraînera la fin du processus.Réponses:
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 correctement une erreur à l'utilisateur. Par exemple, s'il y a une erreur lors de la tentative de lecture d'un fichier d'entrée, vous voudrez peut-être faire quelque chose à ce sujet. Des erreurs peuvent résulter de bogues, mais elles peuvent également être simplement la sortie appropriée pour une entrée donnée.
Les affirmations sont destinées à des choses comme vérifier que les exigences d'une API sont remplies lorsque l'API ne serait normalement pas 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 le vérifieriez normalement pas, mais vous pourriez avoir une assertion pour le vérifier afin que les builds de débogage signalent ce type de bogue. Une assertion doit toujours indiquer un programme fonctionnant de manière incorrecte.
Si vous écrivez un programme dans lequel un arrêt impur pourrait causer un problème, vous voudrez peut-être éviter les assertions. Un comportement non dé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 non défini, ou de 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, elle pourrait potentiellement être interceptée et «gérée» même si cela contredit le but même de l'assertion.
la source
3
place de1
votre code, il ne doit en général pas déclencher d'assertion. Les affirmations ne sont qu'une erreur de programmeur, pas une erreur d'utilisateur de la bibliothèque ou d'application.Les assertions sont destinées au débogage . L'utilisateur de votre code livré ne devrait jamais les voir. Si une assertion est atteinte, votre code doit être corrigé.
CWE-617: Assertion accessible
Les exceptions concernent des circonstances exceptionnelles . S'il en rencontre un, 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'imparsable, c'est normal , car l'entrée de l'utilisateur n'est pas sous votre contrôle et vous devez toujours gérer toutes les situations possibles naturellement. (Par exemple, faites une boucle jusqu'à ce que vous ayez une entrée valide, en disant "Désolé, réessayez" entre les deux.)
la source
Les assertions peuvent être utilisées pour vérifier les invariants internes d'implémentation, 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 faire 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 que vous pouvez ainsi facilement étudier la trace de la pile et les variables. Ceci est beaucoup plus utile pour comprendre l'échec logique que le message d'exception.
la source
Ne pas exécuter de destructeurs à cause de tout abort () n'est pas un comportement indéfini!
Si c'était le cas, alors ce serait un comportement indéfini à appeler
std::terminate()
aussi, et alors quel serait l'intérêt de le fournir?assert()
est tout aussi utile en C ++ qu'en C. Les assertions ne servent pas à gérer les erreurs, mais à abandonner immédiatement le programme.la source
abort()
c'est pour abandonner le programme immédiatement. Vous avez raison de dire que les assertions ne sont pas destinées à la gestion des erreurs, mais assert essaie de gérer l'erreur en abandonnant. Ne devriez-vous pas plutôt lever une exception et laisser l'appelant gérer l'erreur s'il le peut? Après tout, l'appelant est mieux placé pour déterminer si l'échec d'une fonction ne vaut pas la peine de faire autre chose. Peut-être que l'appelant essaie de faire trois choses sans rapport et pourrait encore terminer les deux autres tâches et simplement supprimer celle-ci.assert
est défini pour appelerabort
(lorsque la condition est fausse). Quant à lancer des exceptions, non, ce n'est pas toujours approprié. Certaines choses ne peuvent pas être gérées par l'appelant. L'appelant ne peut pas déterminer si un bogue logique dans une fonction de bibliothèque tierce est récupérable ou si des données corrompues peuvent être corrigées.À mon humble avis, les affirmations servent à vérifier les conditions qui, si elles sont violées, rendent tout le reste absurde. Et par conséquent, vous ne pouvez pas récupérer d'eux ou plutôt, la récupération n'est pas pertinente.
Je les regrouperais en 2 catégories:
Ce sont deux exemples triviaux mais pas trop éloignés de la réalité. Par exemple, pensez aux algorithmes naïfs qui renvoient des index négatifs à utiliser avec des vecteurs. Ou des programmes intégrés dans du matériel personnalisé. Ou plutôt parce que sh * t arrive .
Et s'il y a de telles erreurs de développement, vous ne devriez pas être sûr de 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.
la source