Idiomatische Verwendung von Ausnahmen in C ++

16

In den häufig gestellten Fragen zur isocpp.org-Ausnahme heißt es

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 und den Absturzspeicherauszug für den Entwickler zum Debuggen zu sammeln.

Andererseits definiert die Standardbibliothek std :: logic_error und alle Ableitungen, die mir so vorkommen, als würden sie neben anderen Dingen auch Programmierfehler behandeln. Ist die Übergabe eines leeren Strings an std :: stof (wirft invalid_argument) kein Programmierfehler? Handelt es sich bei der Übergabe einer Zeichenfolge, die andere Zeichen als '1' / '0' enthält, an std :: bitset nicht um einen Programmierfehler? Ist der Aufruf von std :: bitset :: set mit einem ungültigen Index (wird out_of_range auslösen) kein Programmierfehler? Wenn dies nicht der Fall ist, auf was für einen Programmierfehler würde man dann testen? Der auf std :: bitset-Strings basierende Konstruktor existiert nur seit C ++ 11 und sollte daher unter Berücksichtigung der Ausnahmebedingungen entwickelt worden sein. Andererseits haben mir die Leute gesagt, dass logic_error grundsätzlich überhaupt nicht verwendet werden sollte.

Eine andere Regel, die häufig mit Ausnahmen einhergeht, lautet "Ausnahmen nur in Ausnahmefällen verwenden". Aber wie soll eine Bibliotheksfunktion wissen, welche Umstände außergewöhnlich sind? Bei einigen Programmen kann es außergewöhnlich sein, dass eine Datei nicht geöffnet werden kann. Für andere ist es möglicherweise keine Ausnahme, wenn sie keinen Speicher zuweisen können. Dazwischen liegen Hunderte von Fällen. Sie können keinen Socket erstellen? Sie können keine Verbindung herstellen oder Daten in einen Socket oder eine Datei schreiben? Eingaben können nicht analysiert werden? Könnte außergewöhnlich sein, vielleicht auch nicht. Die Funktion selbst kann definitiv nicht allgemein wissen, sie hat keine Ahnung, in welchem ​​Kontext sie aufgerufen wird.

Wie soll ich also entscheiden, ob ich Ausnahmen für eine bestimmte Funktion verwenden soll oder nicht? Es scheint mir, dass der einzig wirklich konsequente Weg darin besteht, sie für jede und jede Fehlerbehandlung oder für nichts zu verwenden. Und wenn ich die Standardbibliothek verwende, wurde diese Wahl für mich getroffen.

cooky451
quelle
6
Sie müssen diesen FAQ-Eintrag sehr sorgfältig lesen . Dies gilt nur für Codierungsfehler, nicht für ungültige Daten, für die Dereferenzierung eines Nullobjekts oder für alles, was mit der allgemeinen Laufzeitschwäche zu tun hat. Bei Behauptungen geht es im Allgemeinen darum, Dinge zu identifizieren , die niemals passieren sollten. Für alles andere gibt es Ausnahmen, Fehlercodes usw.
Robert Harvey
1
@RobertHarvey diese Definition hat immer noch das gleiche Problem - ob etwas ohne menschliches Eingreifen gelöst werden kann oder nicht, ist nur den oberen Schichten eines Programms bekannt.
cooky451
1
Sie werden auf Legalistik aufgehängt. Bewerten Sie die Vor- und Nachteile und entscheiden Sie selbst. Auch der letzte Absatz in Ihrer Frage ... Ich halte das überhaupt nicht für selbstverständlich. Ihr Denken ist sehr schwarz und weiß, wenn die Wahrheit wahrscheinlich eher ein paar Graustufen entspricht.
Robert Harvey
4
Haben Sie versucht, Nachforschungen anzustellen, bevor Sie diese Frage gestellt haben? C ++ - Fehlerbehandlungs-Idiome werden mit ziemlicher Sicherheit im Web ausführlich diskutiert. Ein Verweis auf einen FAQ-Eintrag macht keine gute Recherche. Nachdem Sie Ihre Recherchen durchgeführt haben, müssen Sie sich noch eine eigene Meinung bilden. Lassen Sie mich nicht damit anfangen, wie unsere Programmierschulen anscheinend sinnlose Software-Pattern-Codierungsroboter entwickeln, die nicht wissen, wie sie selbst denken sollen.
Robert Harvey
2
Dies verleiht meiner Theorie die Glaubwürdigkeit, dass eine solche Regel möglicherweise gar nicht existiert. Ich habe einige Leute aus der C ++ Lounge eingeladen, um zu sehen, ob sie Ihre Frage beantworten können, obwohl sie jedes Mal, wenn ich dort hingehe, den Rat geben: "C ++ nicht mehr verwenden, es wird Ihr Gehirn anbraten." Also nehmen Sie ihren Rat auf eigenes Risiko.
Robert Harvey

Antworten:

15

Erstens fühle ich mich verpflichtet, darauf hinzuweisen, dass std::exceptionund seine Kinder vor langer Zeit entworfen wurden. Es gibt eine Reihe von Teilen, die wahrscheinlich (mit ziemlicher Sicherheit) anders wären, wenn sie heute entworfen würden.

Verstehen Sie mich nicht falsch: Es gibt Teile des Designs, die ziemlich gut ausgearbeitet haben, und es gibt ziemlich gute Beispiele für das Entwerfen einer Ausnahmehierarchie für C ++ (z. B. die Tatsache, dass sie im Gegensatz zu den meisten anderen Klassen a gemeinsam haben gemeinsame Wurzel).

Speziell betrachtet logic_errorhaben wir ein Rätsel. Auf der einen Seite, wenn Sie eine vernünftige Wahl in der Sache haben, ist der Rat, den Sie zitiert haben, richtig: Es ist im Allgemeinen am besten, so schnell und geräuschvoll wie möglich zu versagen, damit Fehler behoben werden können.

Ob gut oder schlecht, es ist schwierig, die Standardbibliothek so zu definieren, wie Sie es allgemein tun sollten. Wenn es diese definiert, um das Programm zu verlassen (z. B. abort()bei falscher Eingabe aufzurufen ), ist dies immer der Fall - und es gibt tatsächlich einige Umstände, unter denen dies wahrscheinlich nicht richtig ist Zumindest im implementierten Code.

Dies gilt für Code mit (zumindest weichen) Echtzeitanforderungen und minimaler Strafe für eine falsche Ausgabe. Betrachten Sie beispielsweise ein Chat-Programm. Wenn es darum geht, einige Sprachdaten zu dekodieren und falsche Eingaben zu erhalten, ist es wahrscheinlich, dass ein Benutzer mit einer Millisekunde statischer Aufladung in der Ausgabe viel glücklicher ist als mit einem Programm, das nur vollständig heruntergefahren wird. Ebenso kann es bei der Videowiedergabe akzeptabler sein, für ein oder zwei Bilder die falschen Werte für einige Pixel zu erzeugen, als das Programm zu beenden, da der Eingabestream beschädigt wurde.

Was die Verwendung von Ausnahmen zur Meldung bestimmter Fehlertypen betrifft: Sie haben Recht - derselbe Vorgang kann je nach Verwendungszweck als Ausnahme eingestuft werden oder nicht.

Andererseits liegen Sie auch falsch - die Verwendung der Standardbibliothek erzwingt Ihnen diese Entscheidung (nicht unbedingt). Wenn Sie eine Datei öffnen, verwenden Sie normalerweise einen iostream. Iostreams sind nicht gerade das neueste und beste Design, aber in diesem Fall stimmt es: Sie können einen Fehlermodus festlegen, damit Sie steuern können, ob beim Öffnen einer Datei eine Ausnahme ausgelöst wird oder nicht. Wenn Sie also eine Datei haben, die für Ihre Anwendung wirklich notwendig ist , und wenn Sie diese nicht öffnen, müssen Sie ernsthafte Abhilfemaßnahmen ergreifen, und es kann eine Ausnahme ausgelöst werden, wenn diese Datei nicht geöffnet werden kann. Bei den meisten Dateien, die Sie öffnen möchten, schlagen diese fehl, wenn sie nicht vorhanden sind oder nicht darauf zugegriffen werden kann (dies ist die Standardeinstellung).

Wie Sie sich entscheiden: Ich glaube nicht, dass es eine einfache Antwort gibt. Ob gut oder schlecht, "außergewöhnliche Umstände" sind nicht immer leicht zu messen. Zwar gibt es Fälle, die leicht zu entscheiden sind und [un] außergewöhnlich sein müssen, aber es gibt (und wird wahrscheinlich immer) Fälle, in denen Fragen offen sind oder die Kenntnis des Kontexts erfordern, der außerhalb des Bereichs der jeweiligen Funktion liegt. In solchen Fällen kann es sich zumindest lohnen, ein Design in Betracht zu ziehen, das in etwa diesem Teil von iostreams ähnelt. Hier kann der Benutzer entscheiden, ob ein Fehler zu einer Ausnahmebedingung führt oder nicht. Alternativ ist es durchaus möglich, zwei separate Sätze von Funktionen (oder Klassen usw.) zu haben, von denen einer Ausnahmen auslöst, um einen Fehler anzuzeigen, während der andere andere Mittel verwendet. Wenn Sie diesen Weg gehen,

Jerry Sarg
quelle
9

Der auf std :: bitset-Strings basierende Konstruktor existiert nur seit C ++ 11 und sollte daher unter Berücksichtigung der Ausnahmebedingungen entwickelt worden sein. Andererseits haben mir die Leute gesagt, dass logic_error grundsätzlich überhaupt nicht verwendet werden sollte.

Sie werden das vielleicht nicht glauben, aber verschiedene C ++ - Codierer stimmen dem nicht zu. Das ist der Grund, warum die FAQ eines besagt, aber die Standardbibliothek widerspricht.

Die FAQ befürwortet einen Absturz, da dies einfacher zu debuggen ist. Wenn Sie abstürzen und einen Core-Dump erhalten, haben Sie den genauen Status Ihrer Anwendung. Wenn Sie eine Ausnahme auslösen, verlieren Sie einen Großteil dieses Zustands.

Die Standardbibliothek geht von der Theorie aus, dass es wichtiger als die Debug-Fähigkeit ist, dem Codierer die Möglichkeit zu geben, den Fehler abzufangen und möglicherweise zu behandeln.

Könnte außergewöhnlich sein, vielleicht auch nicht. Die Funktion selbst kann definitiv nicht allgemein wissen, sie hat keine Ahnung, in welchem ​​Kontext sie aufgerufen wird.

Die Idee hier ist, dass, wenn Ihre Funktion nicht weiß, ob die Situation außergewöhnlich ist oder nicht, sie keine Ausnahme auslösen sollte. Es sollte über einen anderen Mechanismus einen Fehlerstatus zurückgeben. Sobald es einen Punkt im Programm erreicht, an dem es weiß, dass der Status außergewöhnlich ist, sollte es die Ausnahme auslösen.

Das hat aber ein eigenes Problem. Wenn ein Fehlerstatus von einer Funktion zurückgegeben wird, können Sie sich möglicherweise nicht daran erinnern, ihn zu überprüfen, und der Fehler wird unbemerkt weitergegeben. Dies führt dazu, dass einige Leute die Ausnahmeregeln aufgeben, um Ausnahmen für jede Art von Fehlerstatus auszulösen.

Insgesamt ist der entscheidende Punkt, dass verschiedene Leute unterschiedliche Vorstellungen haben, wann Ausnahmen zu werfen sind. Sie werden keine einzige zusammenhängende Idee finden. Auch wenn manche Willenskünstler dogmatisch behaupten, dass dies oder das der richtige Weg ist, mit Ausnahmen umzugehen, gibt es keine vereinbarte Theorie.

Sie können Ausnahmen auslösen:

  1. noch nie
  2. Überall
  3. Nur bei Programmierfehlern
  4. Niemals auf Programmierfehler
  5. Nur bei nicht routinemäßigen (außergewöhnlichen) Ausfällen

und jemanden im Internet finden, der Ihnen zustimmt. Sie müssen den Stil übernehmen, der für Sie funktioniert.

Winston Ewert
quelle
Es ist möglicherweise erwähnenswert, dass der Vorschlag, Ausnahmen nur dann zu verwenden, wenn die Umstände wirklich außergewöhnlich sind, von Menschen, die über Sprachen unterrichten, in denen Ausnahmen schlechte Leistungen erbringen , weitgehend befürwortet wurde. C ++ ist keine dieser Sprachen.
Jules
1
@Jules - jetzt, da (Leistung) sicherlich eine eigene Antwort verdient, wo Sie Ihren Anspruch unterstützen. Die Leistung von C ++ - Ausnahmen ist sicherlich ein Problem, vielleicht sogar weniger als anderswo, aber die Aussage, dass "C ++ keine dieser Sprachen ist [wo Ausnahmen eine schlechte Leistung haben]", ist mit Sicherheit umstritten.
Martin Ba
1
@MartinBa - Im Vergleich zu Java ist die C ++ - Ausnahmeleistung um Größenordnungen schneller. Benchmarks legen nahe, dass die Leistung beim Auslösen einer Ausnahme um das 50-fache langsamer ist als bei der Verarbeitung eines Rückgabewerts in C ++, während sie in Java um das 1000-fache langsamer ist. Hinweise, die in diesem Fall für Java geschrieben wurden, sollten nicht ohne weitere Überlegungen auf C ++ angewendet werden, da sich die Leistung zwischen beiden um mehr als eine Größenordnung unterscheidet. Vielleicht hätte ich eher "extrem schlechte Leistung" als "schlechte Leistung" schreiben sollen.
Jules
1
@Jules - danke für diese Zahlen. (alle Quellen?) Ich kann sie glauben, weil Java (und C #) Notwendigkeit , den Stack - Trace zu erfassen, was sicher scheint , wie es wirklich teuer sein könnte. Ich denke immer noch, dass deine anfängliche Reaktion etwas irreführend ist, denn selbst eine 50-fache Verlangsamung ist ziemlich schwer, denke ich. in einer leistungsorientierten Sprache wie C ++.
Martin Ba
2

Viele andere gute Antworten wurden geschrieben, ich möchte nur einen kurzen Punkt hinzufügen.

Die traditionelle Antwort vergleicht, besonders wenn die ISO C ++ - FAQ geschrieben wurde, hauptsächlich "C ++ - Ausnahme" mit "C-artigem Rückkehrcode". Eine dritte Option, "eine Art zusammengesetzten Wert zurückgeben, z. B. a structoder unionoder heutzutage boost::variantoder der (vorgeschlagene) std::expected, wird nicht berücksichtigt.

Vor C ++ 11 war die Option "Composite-Typ zurückgeben" normalerweise sehr schwach. Weil es keine Verschiebungssemantik gab, war das Kopieren von Dingen in und aus einer Struktur möglicherweise sehr teuer. Zu diesem Zeitpunkt war es in der Sprache äußerst wichtig, Ihren Code in Richtung RVO zu formatieren , um die bestmögliche Leistung zu erzielen. Ausnahmen waren wie eine einfache Möglichkeit, einen zusammengesetzten Typ effektiv zurückzugeben, wenn dies sonst ziemlich schwierig wäre.

IMO, nach C ++ 11 sollte diese Option "return a discriminated union", ähnlich der Result<T, E>in Rust heute verwendeten Redewendung , im C ++ - Code häufiger bevorzugt werden. Manchmal ist es wirklich eine einfachere und bequemere Art, Fehler anzuzeigen. Mit Ausnahmen gibt es immer diese Möglichkeit, dass Funktionen, die zuvor nicht geworfen wurden, plötzlich nach einem Refactor geworfen werden können, und Programmierer dokumentieren solche Dinge nicht immer so gut. Wenn der Fehler als Teil des Rückgabewerts in einer diskriminierten Union angegeben wird, wird die Wahrscheinlichkeit, dass der Programmierer den Fehlercode einfach ignoriert, erheblich verringert. Dies ist die übliche Kritik an der Fehlerbehandlung im C-Stil.

Funktioniert normalerweise Result<T, E>wie Boost optional. Sie können mit testen, operator boolob es sich um einen Wert oder einen Fehler handelt. Und dann verwenden Sie say, operator *um auf den Wert oder eine andere "get" -Funktion zuzugreifen. Normalerweise ist dieser Zugriff aus Gründen der Geschwindigkeit deaktiviert. Sie können jedoch festlegen, dass bei einem Debugbuild der Zugriff überprüft wird und eine Zusicherung sicherstellt, dass tatsächlich ein Wert und kein Fehler vorliegt. Auf diese Weise erhält jeder, der nicht richtig nach Fehlern sucht, eine harte Behauptung und kein heimtückischeres Problem.

Ein zusätzlicher Vorteil ist, dass im Gegensatz zu Ausnahmen, bei denen der Stapel nur um eine willkürliche Strecke nach oben geschleudert wird, wenn eine Funktion einen Fehler meldet, bei dem dies zuvor nicht der Fall war, Sie ihn erst kompilieren können, wenn Code wird geändert, um damit umzugehen. Dies macht die Probleme lauter - das traditionelle Problem der "nicht erfassten Ausnahme" wird eher zu einem Fehler bei der Kompilierung als zu einem Laufzeitfehler.

Ich bin ein großer Fan dieses Stils geworden. Normalerweise verwende ich heutzutage entweder diese oder Ausnahmen. Aber ich versuche, die Ausnahmen auf größere Probleme zu beschränken. Bei so etwas wie einem Analysefehler versuche ich expected<T>zum Beispiel zurückzukehren. Dinge wie std::stoiund boost::lexical_castdie eine C ++ - Ausnahme auslösen, wenn ein relativ kleines Problem auftritt: "Zeichenfolge konnte nicht in Zahl konvertiert werden", schmecken mir heutzutage sehr schlecht.

Chris Beck
quelle
1
std::expectedist noch ein nicht angenommener vorschlag, oder?
Martin Ba
Du hast recht, ich denke es wird noch nicht akzeptiert. Es gibt jedoch mehrere Open Source-Implementierungen, und ich habe meine eigenen ein paar Mal umgesetzt, denke ich. Es ist weniger kompliziert als ein Variantentyp, da es nur zwei mögliche Zustände gibt. Die wichtigsten Überlegungen zum Design lauten wie: Welche Schnittstelle möchten Sie genau haben und wie erwartet soll Andrescus <T> sein, wo das Fehlerobjekt eigentlich sein soll exception_ptr, oder möchten Sie nur einen Strukturtyp oder etwas anderes verwenden? so wie das.
Chris Beck
Andrei Alexandrescus Vortrag ist hier: channel9.msdn.com/Shows/Going+Deep/… Er zeigt im Detail, wie man eine solche Klasse aufbaut und welche Überlegungen Sie haben könnten.
Chris Beck
Die vorgeschlagene [[nodiscard]] attributeMethode ist für diese Fehlerbehandlung hilfreich, da sie sicherstellt, dass Sie das Fehlerergebnis nicht versehentlich einfach ignorieren.
CodesInChaos
- Ja, ich wusste, dass AA redet. Ich fand das Design ziemlich seltsam, da man zum Entpacken ( except_ptr) intern eine Ausnahme auslösen musste. Persönlich denke ich, dass ein solches Tool völlig unabhängig von der Ausführung funktionieren sollte. Nur eine Bemerkung.
Martin Ba
1

Dies ist ein sehr subjektives Thema, da es Teil des Designs ist. Und weil Design im Grunde genommen Kunst ist, diskutiere ich diese Dinge lieber als zu diskutieren (ich sage nicht, dass Sie debattieren).

Für mich gibt es zwei Arten von Ausnahmefällen: solche, die sich mit Ressourcen befassen, und solche, die sich mit kritischen Vorgängen befassen. Was als kritisch angesehen werden kann, hängt vom jeweiligen Problem und in vielen Fällen vom Standpunkt des Programmierers ab.

Das Versäumnis, Ressourcen zu erwerben, ist ein Hauptkandidat für das Werfen von Ausnahmen. Die Ressource kann Arbeitsspeicher, Datei, Netzwerkverbindung oder alles andere sein, je nach Problem und Plattform. Ist die Nichtfreigabe einer Ressource eine Ausnahme? Nun, das kommt wieder darauf an. Ich habe noch nichts getan, bei dem die Freigabe des Speichers fehlgeschlagen ist, daher bin ich mir bei diesem Szenario nicht sicher. Das Löschen von Dateien als Teil der Ressourcenfreigabe kann jedoch fehlschlagen und ist für mich fehlgeschlagen. Dieser Fehler hängt normalerweise mit einem anderen Prozess zusammen, der ihn in einer Mehrprozessanwendung geöffnet gelassen hat. Ich vermute, dass andere Ressourcen während der Veröffentlichung fehlschlagen könnten, wie dies bei einer Datei der Fall sein könnte, und es ist normalerweise ein Konstruktionsfehler, der dieses Problem verursacht. Es wäre also besser, ihn zu beheben, als eine Ausnahme zu werfen.

Dann kommt die Aktualisierung der Ressourcen. Dieser Punkt hängt für mich zumindest eng mit dem Aspekt der kritischen Operationen der Anwendung zusammen. Stellen Sie sich eine EmployeeKlasse mit einer Funktion vor UpdateDetails(std::string&), die die Details basierend auf der angegebenen kommagetrennten Zeichenfolge ändert. Ähnlich wie bei der Freigabe von Speicherfehlern fällt es mir schwer, mir vorzustellen, dass die Zuweisung von Membervariablenwerten fehlschlägt, da ich in solchen Bereichen, in denen dies auftreten könnte, keine Erfahrung habe. Es UpdateDetailsAndUpdateFile(std::string&)ist jedoch zu erwarten , dass eine Funktion wie die, die der Name angibt, fehlschlägt. Das nenne ich kritische Operation.

Nun müssen Sie prüfen, ob die so genannte kritische Operation die Auslösung einer Ausnahme rechtfertigt. Ich meine, geschieht die Aktualisierung der Datei am Ende, wie im Destruktor, oder ist es einfach ein paranoider Aufruf, der nach jeder Aktualisierung durchgeführt wird? Gibt es einen Fallback-Mechanismus, der regelmäßig ungeschriebene Objekte schreibt? Was ich sage, ist, dass Sie die Kritikalität der Operation bewerten müssen.

Offensichtlich gibt es viele kritische Vorgänge, die nicht an Ressourcen gebunden sind. Wenn die UpdateDetails()Daten falsch sind, werden die Details nicht aktualisiert und der Fehler muss gemeldet werden, sodass Sie hier eine Ausnahme auslösen würden. Aber stell dir eine Funktion vor wie GiveRaise(). Wenn der besagte Mitarbeiter das Glück hat, einen spitzen Chef zu haben und keine Erhöhung zu erhalten (programmtechnisch gesehen, verhindert der Wert einiger Variablen dies), ist die Funktion im Wesentlichen fehlgeschlagen. Würden Sie hier eine Ausnahme machen? Was ich sage, ist, dass Sie die Notwendigkeit einer Ausnahme bewerten müssen.

Für mich ist Konsistenz in Bezug auf meinen Designansatz wichtiger als die Benutzerfreundlichkeit meiner Klassen. Was ich damit meine ist, ich denke nicht, dass alle Get-Funktionen dies tun müssen und alle Update-Funktionen dies tun müssen, sondern ob eine bestimmte Funktion eine bestimmte Idee in meinem Ansatz anspricht. Auf den ersten Blick könnten die Klassen wie zufällig aussehen, aber wenn die Benutzer (meistens Kollegen aus anderen Teams) darüber schimpfen oder danach fragen, erkläre ich es ihnen und sie scheinen zufrieden zu sein.

Ich sehe viele Leute, die im Grunde genommen Rückgabewerte durch Ausnahmen ersetzen, weil sie C ++ und nicht C verwenden, und dass es eine "nette Trennung der Fehlerbehandlung" usw. gibt, und fordere mich auf, das "Mischen" von Sprachen usw. zu stoppen, von denen ich normalerweise Abstand halte solche Menschen.

vin
quelle
1

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_errorjedoch 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_errorwird, 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_errorobwohl 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 stofund 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 catchals 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 TryParsevs. Parseins 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 stofnur (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 :-)

Martin Ba
quelle