Sollte man von std :: exception ableiten / erben?

15

Während ich meine erste "ernsthafte" C ++ - Bibliothek entwerfe, frage ich mich:

Ist es gut, Ausnahmen von std::exceptionund Nachkommen abzuleiten ?!

Auch nach dem Lesen

Ich bin mir immer noch nicht sicher. Weil ich als Bibliotheksbenutzer neben der üblichen (aber möglicherweise nicht guten) Praxis davon ausgehen würde, dass eine Bibliotheksfunktion std::exceptions nur dann auslöst, wenn Standard-Bibliotheksfunktionen in der Bibliotheksimplementierung fehlschlagen, und nichts dagegen tun kann. Aber immer noch, wenn ich Anwendungscode schreibe, ist das für mich sehr praktisch, und meiner Meinung nach sieht es auch gut aus, einfach einen zu werfen std::runtime_error. Auch meine User können sich ebenfalls auf die definierte Mindestschnittstelle verlassen, wie zum Beispiel what()oder Codes.

Und zum Beispiel, mein Benutzer liefert fehlerhafte Argumente, was wäre bequemer, als ein zu werfen std::invalid_argument, nicht wahr? So kombiniert mit der noch häufigen Verwendung von std :: exception sehe ich in anderen Code: Warum nicht noch weiter gehen und von Ihrer benutzerdefinierten Ausnahmeklasse (zB lib_foo_exception) und auch von ableiten std::exception.

Gedanken?

Superlokkus
quelle
Ich bin nicht sicher, ob ich folge. Nur weil Sie erben von std::exceptionnicht Sie bedeutet werfen ein std::exception. Auch std::runtime_errorerbt von std::exceptionin erster Linie, und die what()Methode kommt std::exceptionnicht von std::runtime_error. Und Sie sollten auf jeden Fall Ihre eigenen Ausnahmeklassen erstellen, anstatt generische Ausnahmen wie std::runtime_error.
Vincent Savard
3
Der Unterschied besteht darin, dass der Bibliotheksbenutzer , wenn sich meine lib_foo_exceptionKlasse davon ableitet std::exception, lib_foo_exceptionnur fangen würde std::exception, zusätzlich dazu, wenn er nur die Bibliothek fängt. Also könnte ich auch fragen, ob meine Bibliotheksausnahmestammklasse von std :: exception erbt .
Superlokkus
3
@LightnessRacesinOrbit Ich meine "... zusätzlich zu", wie "Wie viele Möglichkeiten gibt es zu fangen lib_foo_exception?" Mit dem Erben von std::exceptionkönnen Sie es durch catch(std::exception)ODER durch tun catch(lib_foo_exception). Ohne Ableitung aus std::exception, würden Sie es fangen , wenn und nur wenn durch catch(lib_foo_exception).
Superlokkus
2
@ Superlokkus: Wir ignorieren das irgendwie catch(...). Es ist vorhanden, weil die Sprache den von Ihnen in Betracht gezogenen Fall (und das "schlechte Benehmen" von Bibliotheken) berücksichtigt, aber dies ist keine moderne Best Practice.
Leichtigkeit Rennen mit Monica
1
Ein Großteil des Entwurfs der Ausnahmebehandlung in C ++ neigt dazu, gröbere, allgemeinere catchSites und ebenso gröbere Transaktionen anzuregen, die eine Benutzerendoperation modellieren. Wenn Sie es mit Sprachen vergleichen, die die Idee des generalisierten Fangens von nicht fördern std::exception&, haben sie beispielsweise oft viel mehr Code mit Zwischenblöcken try/catch, die mit sehr spezifischen Fehlern behaftet sind, was die Allgemeingültigkeit der Ausnahmebehandlung etwas verringert, wenn sie beginnt zu platzieren Eine viel stärkere Betonung liegt auf der manuellen Fehlerbehandlung und auch auf allen unterschiedlichen Fehlern, die möglicherweise auftreten können.

Antworten:

29

Alle Ausnahmen sollten von erben std::exception.

Nehmen wir zum Beispiel an, ich muss anrufen ComplexOperationThatCouldFailABunchOfWays()und möchte alle Ausnahmen behandeln, die es auslösen könnte. Wenn alles von erbt std::exception, ist das einfach. Ich brauche nur einen einzigen catchBlock und habe eine Standardschnittstelle ( what()), um Details zu erhalten.

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

Wenn Ausnahmen NICHT von erben std::exception, wird dies viel hässlicher:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

Über das Werfen runtime_erroroder das invalid_argumentErstellen eigener std::exceptionUnterklassen zum Werfen: Als Faustregel gilt, wenn ich einen bestimmten Fehlertyp anders als andere Fehler behandeln muss (dh wenn ich einen separaten catchBlock benötige), eine neue Unterklasse einzuführen .

  • Wenn ich für jeden erdenklichen Fehlertyp eine neue Ausnahmesubklasse einführe, auch wenn ich sie nicht separat behandeln muss, führt dies zu einer erheblichen Klassenverbreitung.
  • Wenn ich vorhandene Unterklassen wiederverwende, um etwas Bestimmtes zu bedeuten (dh wenn ein hierruntime_error geworfener Wert etwas anderes als einen generischen Laufzeitfehler bedeutet), laufe ich Gefahr, mit anderen Verwendungen der vorhandenen Unterklasse in Konflikt zu geraten.
  • Wenn ich nicht brauche einen Fehler speziell zu handhaben , und wenn der Fehler , dass ich werfen entspricht genau einen der vorhandene Standard - Bibliothek Fehler (wie invalid_argument), dann wieder verwende ich die vorhandene Klasse. In diesem Fall sehe ich keinen großen Vorteil darin, eine neue Klasse hinzuzufügen. (Die C ++ Core Guidelines stimmen hier nicht mit mir überein - sie empfehlen, immer Ihre eigenen Klassen zu verwenden.)

Die C ++ Core Guidelines enthalten weitere Diskussionen und Beispiele.

Josh Kelley
quelle
All diese Etzeichen! C ++ ist komisch.
SuperJedi224
2
@ SuperJedi224 und all diese verschiedenen Buchstaben! Englisch ist komisch.
Johannes
Diese Begründung ergibt für mich keinen Sinn. Ist das nicht was catch (...)(mit der wörtlichen Ellipse) ist für?
Maxpm
1
@Maxpm catch (...)ist nur nützlich, wenn Sie nichts mit dem tun müssen, was geworfen wird. Wenn Sie etwas tun möchten, z. B. die spezifische Fehlermeldung wie in meinem Beispiel anzeigen oder protokollieren, müssen Sie wissen, um was es sich handelt.
Josh Kelley
9

Als Bibliotheksbenutzer würde ich davon ausgehen, dass eine Bibliotheksfunktion nur dann std :: exceptions auslöst, wenn Standard-Bibliotheksfunktionen in der Bibliotheksimplementierung fehlschlagen, und nichts dagegen tun kann

Das ist eine falsche Annahme.

Die Standardausnahmetypen sind für die "allgemeine" Verwendung vorgesehen. Sie sind nicht nur für die Verwendung durch die Standardbibliothek vorgesehen.

Ja, alles letztendlich erben lassen std::exception. Oft geht es darum, von std::runtime_erroroder zu erben std::logic_error. Was auch immer für die Ausnahmeklasse geeignet ist, die Sie implementieren.

Dies ist natürlich subjektiv - einige populäre Bibliotheken ignorieren die Standardausnahmetypen vollständig, vermutlich, um die Bibliotheken von der Standardbibliothek zu entkoppeln. Persönlich finde ich das extrem egoistisch! Es macht es sehr viel schwieriger, Ausnahmen zu fangen.

Wenn ich persönlich spreche, werfe ich oft einfach ein std::runtime_errorund bin fertig damit. Aber das ist eine Diskussion darüber, wie detailliert Ihre Ausnahmeklassen erstellt werden sollen, was nicht Ihre Frage ist.

Leichtigkeit Rennen mit Monica
quelle