Ich habe gesehen, dass mindestens eine zuverlässige Quelle (eine von mir verwendete C ++ - Klasse) empfohlen hat, von anwendungsspezifischen Ausnahmeklassen in C ++ zu erben std::exception
. Die Vorteile dieses Ansatzes sind mir nicht klar.
In C # liegen die Gründe für das Erben auf der ApplicationException
Hand: Sie erhalten eine Handvoll nützlicher Methoden, Eigenschaften und Konstruktoren und müssen nur das hinzufügen oder überschreiben, was Sie benötigen. Mit std::exception
scheint es , dass alles , was man eine bekommen , ist what()
Methode zu überschreiben, die Sie könnten genauso gut selbst erstellen.
Welche Vorteile bietet die Verwendung std::exception
als Basisklasse für meine anwendungsspezifische Ausnahmeklasse , wenn überhaupt ? Gibt es gute Gründe, nicht zu erben std::exception
?
quelle
Antworten:
Der Hauptvorteil ist, dass Code, der Ihre Klassen verwendet, nicht genau wissen muss, was Sie
throw
tun, sondern nurcatch
diestd::exception
.Bearbeiten: Wie Martin und andere angemerkt haben, möchten Sie tatsächlich von einer der
std::exception
im<stdexcept>
Header deklarierten Unterklassen ableiten .quelle
std::exception
wird in der Kopfzeile definiert<exception>
( Beweis ).-1
Das Problem dabei
std::exception
ist, dass es keinen Konstruktor (in den Standard-kompatiblen Versionen) gibt, der eine Nachricht akzeptiert.Daher leite ich lieber ab
std::runtime_error
. Dies ist abgeleitet von,std::exception
aber seine Konstruktoren ermöglichen es Ihnen, einen C-String oder astd::string
an den Konstruktor zu übergeben, derchar const*
beimwhat()
Aufruf (als a ) zurückgegeben wird.quelle
Grund für das Erben von
std::exception
ist die "Standard" -Basisklasse für Ausnahmen. Daher ist es für andere Teammitglieder selbstverständlich, dies zu erwarten und die Basis zu fangenstd::exception
.Wenn Sie nach Bequemlichkeit suchen, können Sie von
std::runtime_error
diesemstd::string
Konstruktor erben .quelle
std::exception
ist eine ausgezeichnete Idee, sich von anderen Standardausnahmen abzuleiten . Was Sie nicht tun sollten, ist von mehr als einem zu erben .Ich habe einmal an der Bereinigung einer großen Codebasis teilgenommen, in der die vorherigen Autoren Ints, HRESULTS, std :: string, char *, zufällige Klassen ... überall verschiedene Dinge geworfen hatten; Nennen Sie einfach einen Typ und er wurde wahrscheinlich irgendwo hingeworfen. Und überhaupt keine gemeinsame Basisklasse. Glauben Sie mir, die Dinge waren viel aufgeräumter, als wir den Punkt erreichten, dass alle geworfenen Typen eine gemeinsame Basis hatten, die wir fangen konnten und wussten, dass nichts vorbei kommen würde. Bitte tun Sie sich selbst (und denjenigen, die Ihren Code in Zukunft pflegen müssen) einen Gefallen und tun Sie dies von Anfang an so.
quelle
Sie sollten von boost :: exception erben . Es bietet viel mehr Funktionen und gut verstandene Möglichkeiten, zusätzliche Daten zu übertragen. Wenn Sie Boost nicht verwenden , ignorieren Sie diesen Vorschlag.
quelle
Ja, Sie sollten ableiten von
std::exception
.Andere haben geantwortet, dass
std::exception
das Problem besteht, dass Sie keine Textnachricht an sie weitergeben können. Es ist jedoch im Allgemeinen keine gute Idee, zu versuchen, eine Benutzernachricht am Punkt des Wurfs zu formatieren. Verwenden Sie stattdessen das Ausnahmeobjekt, um alle relevanten Informationen zur Fangstelle zu transportieren, die dann eine benutzerfreundliche Nachricht formatieren kann.quelle
std::exception
die Informationen der Ausnahme ableiten und tragen kann . Aber wer ist für das Erstellen / Formatieren der Fehlermeldung verantwortlich? Die::what()
Funktion oder etwas anderes?Der Grund, warum Sie möglicherweise erben möchten,
std::exception
liegt darin, dass Sie eine Ausnahme auslösen können, die gemäß dieser Klasse abgefangen wird, z.quelle
Es gibt ein Problem mit der Vererbung, über das Sie Bescheid wissen sollten, nämlich das Aufteilen von Objekten. Wenn Sie
throw e;
einen Wurfausdruck schreiben , wird ein temporäres Objekt initialisiert, das als Ausnahmeobjekt bezeichnet wird und dessen Typ durch Entfernen aller Lebenslaufqualifizierer der obersten Ebene aus dem statischen Typ des Operanden von bestimmt wirdthrow
. Das könnte nicht das sein, was Sie erwarten. Beispiel für ein Problem finden Sie hier .Es ist kein Argument gegen Vererbung, es ist nur eine Information, die man wissen muss.
quelle
throw;
ist in Ordnung, aber es ist nicht offensichtlich, dass Sie so etwas schreiben sollten.raise()
als solche hinzufügen :virtual void raise() { throw *this; }
. Denken Sie daran, es in jeder abgeleiteten Ausnahme zu überschreiben.Unterschied: std :: runtime_error vs std :: exception ()
Ob Sie davon erben sollten oder nicht, liegt bei Ihnen. Standard
std::exception
und seine Standardnachkommen schlagen eine mögliche Ausnahmehierarchiestruktur (Unterteilung inlogic_error
Subhierarchie undruntime_error
Subhierarchie) und eine mögliche Ausnahmeobjektschnittstelle vor. Wenn es dir gefällt - benutze es. Wenn Sie aus irgendeinem Grund etwas anderes benötigen, definieren Sie Ihr eigenes Ausnahmesystem.quelle
Da die Sprache bereits eine std :: Ausnahme auslöst, müssen Sie sie trotzdem abfangen, um eine anständige Fehlerberichterstattung zu ermöglichen. Sie können denselben Fang auch für alle unerwarteten Ausnahmen verwenden. Außerdem würde fast jede Bibliothek, die Ausnahmen auslöst, diese von std :: exception ableiten.
Mit anderen Worten, es ist entweder
oder
Und die zweite Option ist definitiv besser.
quelle
Wenn alle Ihre möglichen Ausnahmen davon herrühren
std::exception
, kann Ihr Fangblock einfachcatch(std::exception & e)
und sicher sein, alles zu erfassen.Sobald Sie die Ausnahme erfasst haben, können Sie diese
what
Methode verwenden, um weitere Informationen abzurufen. C ++ unterstützt keine Ententypisierung, daherwhat
würde eine andere Klasse mit einer Methode einen anderen Fang und einen anderen Code erfordern, um sie zu verwenden.quelle
Ob von einem Standardausnahmetyp abgeleitet werden soll oder nicht, ist die erste Frage. Auf diese Weise wird ein einziger Ausnahmebehandler für alle Standardbibliotheksausnahmen und Ihre eigenen aktiviert, es werden jedoch auch solche Catch-Them-All-Handler empfohlen. Das Problem ist, dass man nur Ausnahmen abfangen sollte, mit denen man umgehen kann. In main () ist es beispielsweise wahrscheinlich eine gute Sache, alle std :: -Ausnahmen abzufangen, wenn die Zeichenfolge what () vor dem Beenden als letzter Ausweg protokolliert wird. An anderer Stelle ist es jedoch unwahrscheinlich, dass dies eine gute Idee ist.
Wenn Sie sich entschieden haben, ob Sie von einem Standardausnahmetyp ableiten möchten oder nicht, stellt sich die Frage, welcher die Basis sein soll. Wenn Ihre Anwendung i18n nicht benötigt, ist das Formatieren einer Nachricht am Anrufstandort möglicherweise genauso gut wie das Speichern von Informationen und das Generieren der Nachricht am Anrufstandort. Das Problem ist, dass die formatierte Nachricht möglicherweise nicht benötigt wird. Verwenden Sie besser ein faules Nachrichtengenerierungsschema - möglicherweise mit vorab zugewiesenem Speicher. Wenn die Nachricht dann benötigt wird, wird sie beim Zugriff generiert (und möglicherweise im Ausnahmeobjekt zwischengespeichert). Wenn die Nachricht beim Auslösen generiert wird, wird daher ein std :: exception-Derivat wie std :: runtime_error als Basisklasse benötigt. Wenn die Nachricht träge generiert wird, ist std :: exception die geeignete Basis.
quelle
Ein weiterer Grund für Unterklassenausnahmen ist ein besserer Entwurfsaspekt bei der Arbeit an großen gekapselten Systemen. Sie können es für Dinge wie Validierungsnachrichten, Benutzerabfragen, schwerwiegende Controller-Fehler usw. wiederverwenden. Anstatt alle Ihre Validierungsnachrichten wie Nachrichten neu zu schreiben oder neu zu verknüpfen, können Sie sie einfach in der Hauptquelldatei "abfangen", aber den Fehler an einer beliebigen Stelle in Ihrer gesamten Klassengruppe auslösen.
Beispiel: Eine schwerwiegende Ausnahme beendet das Programm, ein Validierungsfehler löscht nur den Stapel und eine Benutzerabfrage stellt dem Endbenutzer eine Frage.
Auf diese Weise können Sie auch dieselben Klassen auf unterschiedlichen Schnittstellen wiederverwenden. Beispiel: Eine Windows-Anwendung kann ein Meldungsfeld verwenden, ein Webdienst zeigt HTML an und das Berichtssystem protokolliert es und so weiter.
quelle
Obwohl diese Frage ziemlich alt ist und bereits häufig beantwortet wurde, möchte ich nur einen Hinweis zur ordnungsgemäßen Ausnahmebehandlung in C ++ 11 hinzufügen, da ich dies in Diskussionen über Ausnahmen immer wieder vermisse:
Verwenden Sie
std::nested_exception
undstd::throw_with_nested
In StackOverflow wird hier und hier beschrieben , wie Sie eine Rückverfolgung Ihrer Ausnahmen in Ihrem Code erhalten können, ohne dass ein Debugger oder eine umständliche Protokollierung erforderlich ist, indem Sie einfach einen geeigneten Ausnahmebehandler schreiben, der verschachtelte Ausnahmen erneut auslöst.
Da Sie dies mit jeder abgeleiteten Ausnahmeklasse tun können, können Sie einer solchen Rückverfolgung viele Informationen hinzufügen! Sie können sich auch mein MWE auf GitHub ansehen, wo ein Backtrace ungefähr so aussehen würde:
Sie müssen nicht einmal eine Unterklasse
std::runtime_error
erstellen, um beim Auslösen einer Ausnahme zahlreiche Informationen zu erhalten.Der einzige Vorteil, den ich in Unterklassen sehe (anstatt nur zu verwenden
std::runtime_error
), ist, dass Ihr Ausnahmebehandler Ihre benutzerdefinierte Ausnahme abfangen und etwas Besonderes tun kann. Beispielsweise:quelle