Premièrement, comme d'autres l'ont déclaré, les choses ne sont pas aussi claires en C ++, à mon humble avis, principalement parce que les exigences et les contraintes sont un peu plus variées en C ++ que dans d'autres langages, en particulier. C # et Java, qui ont des problèmes d'exception "similaires".
Je vais exposer sur l'exemple std :: stof:
passer une chaîne vide à std :: stof (lancera invalid_argument) pas une erreur de programmation
Le contrat de base , comme je le vois, de cette fonction est qu'elle essaie de convertir son argument en un flottant, et tout échec à le faire est signalé par une exception. Les deux exceptions possibles sont dérivées, logic_error
mais pas dans le sens d'erreur de programmeur, mais dans le sens de "l'entrée ne peut jamais être convertie en flottant".
Ici, on peut dire que a logic_error
est utilisé pour indiquer que, étant donné que l'entrée (runtime), c'est toujours une erreur d'essayer de la convertir - mais c'est le travail de la fonction de le déterminer et de vous le dire (via une exception).
Note latérale: Dans cette vue, un runtime_error
pourrait être considéré comme quelque chose qui, étant donné la même entrée à une fonction, pourrait théoriquement réussir pour différentes exécutions. (par exemple, une opération sur fichier, accès DB, etc.)
Note supplémentaire: La bibliothèque d'expressions régulières C ++ a choisi de dériver son erreur runtime_error
bien qu'il existe des cas où elle pourrait être classée de la même manière qu'ici (modèle d'expression régulière non valide).
Cela montre juste, à mon humble avis, que le regroupement en logic_
ou runtime_
erreur est assez flou en C ++ et n'aide pas vraiment beaucoup dans le cas général (*) - si vous devez gérer des erreurs spécifiques, vous devez probablement rattraper plus bas que les deux.
(*): Cela ne veut pas dire qu'un seul morceau de code ne doit pas être cohérent, mais si vous jetez runtime_
ou logic_
ou custom_
somethings est vraiment pas si important que cela, je pense.
Pour commenter les deux stof
et bitset
:
Les deux fonctions prennent des chaînes comme argument, et dans les deux cas, c'est:
- non trivial pour vérifier si l'appelant est valide (par exemple, dans le pire des cas, vous devrez répliquer la logique de la fonction; dans le cas d'un jeu de bits, il n'est pas immédiatement clair si une chaîne vide est valide, alors laissez le ctor décider)
- Il est déjà de la responsabilité de la fonction "d'analyser" la chaîne, elle doit donc déjà valider la chaîne, il est donc logique qu'elle signale une erreur pour "utiliser" la chaîne uniformément (et dans les deux cas, c'est une exception) .
la règle qui revient fréquemment avec les exceptions est "n'utiliser les exceptions que dans des circonstances exceptionnelles". Mais comment une fonction de bibliothèque est-elle censée savoir quelles circonstances sont exceptionnelles?
Cette déclaration a, à mon humble avis, deux racines:
Performances : si une fonction est appelée dans un chemin critique et que le cas "exceptionnel" n'est pas exceptionnel, c'est-à-dire qu'un nombre important de passes impliquera de lever une exception, alors payer à chaque fois pour la machine de déroulement d'exception n'a pas de sens et peut être trop lent.
Localité de la gestion des erreurs : si une fonction est invoquée et que l'exception est immédiatement interceptée et traitée, il est inutile de lancer une exception, car la gestion des erreurs sera plus détaillée avec le catch
qu'avec avec if
.
Exemple:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Voici où des fonctions comme TryParse
vs Parse
entrent en jeu: une version pour quand le code local attend que la chaîne analysée soit valide, une version lorsque le code local suppose qu'il est réellement prévu (c'est-à-dire non exceptionnel) que l'analyse échoue.
En effet, stof
est juste (défini comme) un wrapper strtof
, donc si vous ne voulez pas d'exceptions, utilisez-le.
Alors, comment suis-je censé décider si je dois utiliser des exceptions ou non pour une fonction particulière?
À mon humble avis, vous avez deux cas:
Fonction de type "bibliothèque" (réutilisée souvent dans des contextes différents): vous ne pouvez pas décider. Fournissez éventuellement les deux versions, peut-être une qui signale une erreur et une encapsuleuse qui convertit l'erreur renvoyée en exception.
Fonction "Application" (spécifique pour un blob de code d'application, peut être réutilisée, mais est limitée par le style de gestion des erreurs des applications, etc.): Ici, elle devrait souvent être assez claire. Si le (s) chemin (s) de code appelant les fonctions gèrent les exceptions de manière saine et utile, utilisez les exceptions pour signaler toute erreur (mais voir ci-dessous) . Si le code d'application est plus facile à lire et à écrire pour un style de retour d'erreur, utilisez-le par tous les moyens.
Bien sûr, il y aura des endroits entre les deux - utilisez simplement ce dont vous avez besoin et souvenez-vous de YAGNI.
Enfin, je pense que je devrais revenir à la déclaration FAQ,
N'utilisez pas throw pour indiquer une erreur de codage lors de l'utilisation d'une fonction. Utilisez assert ou un autre mécanisme pour envoyer le processus dans un débogueur ou pour bloquer le processus ...
Je souscris à cela pour toutes les erreurs qui indiquent clairement que quelque chose est gravement gâché ou que le code appelant ne savait clairement pas ce qu'il faisait.
Mais lorsque cela est approprié est souvent très spécifique à l'application, donc voir ci-dessus domaine de bibliothèque vs domaine d'application.
Cela revient à la question de savoir si et comment valider les conditions préalables à l' appel , mais je n'entrerai pas dans les détails, répondez déjà trop longtemps :-)