Erstens, wie andere bereits festgestellt haben, sind die Dinge in C ++ nicht so eindeutig, hauptsächlich deshalb, weil die Anforderungen und Einschränkungen in C ++ etwas vielfältiger sind als in anderen Sprachen, insb. C # und Java, die "ähnliche" Ausnahmefehler aufweisen.
Ich werde das Beispiel std :: stof belichten:
Übergeben eines leeren Strings an std :: stof (wirft invalid_argument), kein Programmierfehler
Der Grundvertrag , wie ich es sehe, diese Funktion ist , dass sie versucht es das Argument mit einem Schwimmer zu konvertieren, und jeder Ausfall , dies zu tun wird durch eine Ausnahme gemeldet. Beide möglichen Ausnahmen ergeben sich, logic_error
jedoch nicht im Sinne eines Programmiererfehlers, sondern im Sinne von "Die Eingabe kann niemals in einen Gleitkommawert umgewandelt werden".
Hier kann man sagen, dass a verwendet logic_error
wird, um anzuzeigen, dass es angesichts dieser (Laufzeit-) Eingabe immer ein Fehler ist, es zu konvertieren - aber es ist die Aufgabe der Funktion, dies zu bestimmen und Ihnen (über eine Ausnahme) mitzuteilen.
Randnotiz: In dieser Ansicht runtime_error
könnte a als etwas angesehen werden, das bei gleicher Eingabe für eine Funktion theoretisch für verschiedene Läufe erfolgreich sein könnte. (zB eine Dateioperation, DB-Zugriff, etc.)
Weiterer Side Hinweis: Die C ++ Bibliothek Regex wählte aus seinem Fehler ableiten , runtime_error
obwohl es Fälle gibt , in denen es könnte das gleiche wie hier (ungültiger RegexMuster) klassifiziert werden.
Dies zeigt nur, meiner Meinung nach, dass Gruppierung in logic_
oder runtime_
Fehler in C ++ ziemlich unscharf ist und im allgemeinen Fall nicht wirklich viel hilft (*) - wenn Sie bestimmte Fehler behandeln müssen, müssen Sie wahrscheinlich weniger als die beiden abfangen.
(*): Das ist nicht zu sagen , dass ein einzelnes Stück Code nicht konsistent sein sollte, aber ob Sie werfen runtime_
oder logic_
oder custom_
Somethings ist wirklich nicht so wichtig, denke ich.
Kommentar zu beiden stof
und bitset
:
Beide Funktionen nehmen Strings als Argument und in beiden Fällen ist es:
- Es ist nicht trivial, für den Aufrufer zu prüfen, ob eine bestimmte Zeichenfolge gültig ist (z. B. im schlimmsten Fall müsste die Funktionslogik repliziert werden; im Fall von Bitset ist nicht sofort klar, ob eine leere Zeichenfolge gültig ist, lassen Sie den CTOR entscheiden).
- Es liegt bereits in der Verantwortung der Funktion, die Zeichenfolge zu "analysieren". Daher muss die Zeichenfolge bereits validiert werden. Daher ist es sinnvoll, einen Fehler zu melden, um die Zeichenfolge einheitlich zu "verwenden" (und dies ist in beiden Fällen eine Ausnahme). .
Regel, die häufig mit Ausnahmen auftritt, ist "nur Ausnahmen in Ausnahmefällen verwenden". Aber wie soll eine Bibliotheksfunktion wissen, welche Umstände außergewöhnlich sind?
Diese Aussage hat meiner Meinung nach zwei Wurzeln:
Leistung : Wenn eine Funktion in einem kritischen Pfad aufgerufen wird und der "Ausnahmefall" kein Ausnahmefall ist, dh bei einer signifikanten Anzahl von Durchläufen eine Ausnahme ausgelöst wird, ist es nicht sinnvoll, jedes Mal für die Ausnahmeabwicklungsmaschine zu zahlen und möglicherweise zu langsam.
Lokalität der Fehlerbehandlung : Wenn eine Funktion aufgerufen wird und die Ausnahme sofort abgefangen und verarbeitet wird, ist es wenig sinnvoll, eine Ausnahme auszulösen, da die Fehlerbehandlung bei der ausführlicher ist catch
als bei einer if
.
Beispiel:
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
}
Hier kommen Funktionen wie TryParse
vs. Parse
ins Spiel: Eine Version, bei der der lokale Code erwartet, dass die analysierte Zeichenfolge gültig ist, eine Version, bei der der lokale Code davon ausgeht, dass tatsächlich erwartet wird (dh nicht außergewöhnlich), dass das Analysieren fehlschlägt.
Ist in der Tat stof
nur (definiert als) ein Wrapper strtof
, wenn Sie also keine Ausnahmen wollen, verwenden Sie diesen.
Wie soll ich also entscheiden, ob ich Ausnahmen für eine bestimmte Funktion verwenden soll oder nicht?
IMHO, Sie haben zwei Fälle:
"Bibliothek" -ähnliche Funktion (wird häufig in verschiedenen Kontexten wiederverwendet): Sie können sich im Grunde nicht entscheiden. Geben Sie möglicherweise beide Versionen an, möglicherweise eine, die einen Fehler meldet, und eine Wrappper-Version, die den zurückgegebenen Fehler in eine Ausnahme umwandelt.
"Application" -Funktion (spezifisch für einen Blob von Anwendungscode, kann teilweise wiederverwendet werden, wird jedoch durch den Fehlerbehandlungsstil der Apps usw. eingeschränkt): Hier sollte es häufig ziemlich eindeutig sein. Wenn die Codepfade, die die Funktionen aufrufen, Ausnahmen auf vernünftige und nützliche Weise behandeln, verwenden Sie Ausnahmen, um einen Fehler (siehe unten) zu melden . Wenn der Anwendungscode für einen Fehlerrückgabestil leichter gelesen und geschrieben werden kann, verwenden Sie dies auf jeden Fall.
Natürlich wird es dazwischen Plätze geben - benutze einfach was nötig ist und erinnere dich an YAGNI.
Zuletzt denke ich, ich sollte zur FAQ-Erklärung zurückkehren,
Verwenden Sie throw nicht, um einen Codierungsfehler bei der Verwendung einer Funktion anzuzeigen. Verwenden Sie assert oder einen anderen Mechanismus, um den Prozess entweder in einen Debugger zu senden oder um den Prozess zum Absturz zu bringen.
Ich abonniere dies für alle Fehler, die eindeutig darauf hinweisen, dass etwas stark durcheinander ist oder der aufrufende Code eindeutig nicht wusste, was er tat.
Aber wenn dies angebracht ist, ist es oft sehr anwendungsspezifisch, siehe oben Bibliotheksdomäne vs. Anwendungsdomäne.
Dies fällt zurück auf die Frage, ob und wie zu validieren Aufruf Voraussetzungen , aber ich werde nicht in die, Antwort schon zu lange :-)