Warum hat der C ++ - Standard die std::exception
-Klassen erfunden? Was ist ihr Vorteil? Mein Grund zu fragen ist dies:
Funktioniert gut. Später, wenn ich brauche, kann ich einfach meine eigenen "Ausnahme" -Typen machen. Also warum sollte ich mich mit std::exception
kümmern?
Warum hat der C ++ Standard die
std::exception
-Klassen erfunden? Was ist ihr Vorteil?
Es bietet eine generische und konsistente Schnittstelle für Ausnahmen, die von der Standardbibliothek ausgelöst werden. Alle von der Standardbibliothek generierten Ausnahmen werden von std::exception
übernommen.
Beachten Sie, dass Standardbibliotheks-APIs eine Reihe verschiedener Arten von Ausnahmen auslösen können, um ein paar Beispiele zu nennen:
std::bad_alloc
std::bad_cast
std::bad_exception
std::bad_typeid
std::logic_error
std::runtime_error
std::bad_weak_ptr | C++11
std::bad_function_call | C++11
std::ios_base::failure | C++11
std::bad_variant_access | C++17
und so weiter ...
std::exception
ist die Basisklasse für alle diese Ausnahmen:
, die eine Basisklasse für alle diese Ausnahmen bereitstellt, ermöglicht es Ihnen, mehrere Ausnahmen mit einem allgemeinen Ausnahmebehandler zu behandeln.
Wenn ich brauche, kann ich einfach meine eigenen "Ausnahme" -Typen erstellen. Also warum sollte ich mich mit std::exception
kümmern?
Wenn Sie Ihre benutzerdefinierte Ausnahmeklasse benötigen, gehen Sie vor und machen Sie eine. Aber std::exception
macht Ihre Arbeit einfacher, da es bereits eine Menge Funktionalität bietet, die eine gute Ausnahmeklasse haben sollte. Es bietet Ihnen die Leichtigkeit, daraus abzuleiten und notwendige Funktionen ( insbesondere std::exception::what()
) für Ihre Klassenfunktionalität zu übersteuern.
Dies gibt Ihnen 2 Vorteile der std::exception
Handler,
Bild mit freundlicher Genehmigung: Ссылка
Warum hat der C ++ Standard die std :: exception classes erfunden? Was ist ihr Vorteil?
Mit verschiedenen Arten von Ausnahmen können Sie bestimmte Typen von Fehlern abfangen. Das Ableiten von Ausnahmen von einer gemeinsamen Basis ermöglicht Granularität beim Erfassen allgemeinerer oder spezifischer Fehler abhängig von den Umständen.
In C ++ ist bereits ein type System vorhanden, daher war die Standardisierung von Fehlerzeichenfolgen unnötig, wenn Sie explizit eine Ausnahme eines gewünschten Typs in der Sprache erstellen können.
std :: exception und seine abgeleiteten Klassen bestehen aus zwei Hauptgründen:
Die Standardbibliothek muss eine Art von Ausnahmehierarchie haben in außergewöhnlichen Umständen werfen. Es wäre unangebracht Wirf immer eine std :: string , weil du keinen sauberen Weg dazu hättest zielen Sie auf bestimmte Arten von Fehlern ab.
Bereitstellung einer erweiterbaren klassenbasierten Schnittstelle für Bibliothekshersteller, um die grundlegendsten Fehlertypen zu erzeugen und eine allgemeine Fallback-Funktion für Benutzer bereitzustellen. Möglicherweise möchten Sie mehr Fehlermetadaten als eine einfache Zeichenfolge was () bereitstellen, damit die Person, die Ihren Fehler abfängt, intelligenter davon wiederherstellen kann.
Gleichzeitig ermöglicht std :: exception als allgemeine Basis einen allgemeinen Catchall, der weniger umfassend ist als ... , wenn sich der Benutzer nur für diese Fehlermeldung interessiert .
Wenn alles, was Sie tun, Drucken und Beenden ist, spielt es keine Rolle, aber Sie können auch std :: runtime_error verwenden, das von std :: exception für die Bequemlichkeit des Fangens.
Später, wenn ich brauche, kann ich einfach meine eigenen "Ausnahme" -Typen erstellen. Warum sollte ich mich mit std :: exception beschäftigen?
Wenn Sie von std :: runtime_error erben und Ihren eigenen benutzerdefinierten Fehlertyp verwenden, können Sie Fehlermetadaten rückwirkend hinzufügen, ohne Catch-Blöcke neu schreiben zu müssen! Im Gegensatz dazu müssten Sie, wenn Sie Ihr Design für die Fehlerbehandlung geändert haben, alle Ihre std :: string -Fänge neu schreiben, da Sie nicht sicher von std :: string erben können. Es ist keine zukunftsweisende Designentscheidung.
Wenn Ihnen das im Moment nicht so schlecht erscheint, stellen Sie sich vor, dass Ihr Code in mehreren Projekten als Shared Library mit verschiedenen Programmierern geteilt wird, die daran arbeiten. Die Migration auf die neue Version Ihrer Bibliothek würde zu einem Problem werden.
Dies erwähnt nicht einmal, dass std :: string beim Kopieren, Konstruieren oder Zugriff von Zeichen eigene Ausnahmen auslösen kann!
Boosts Website hat einige gute Richtlinien in der Ausnahmebehandlung und Klassenkonstruktion hier .
Ich schreibe einen Netzwerkcode und verwende einen Drittanbieter Bibliothek. Bei einer ungültigen IP-Adresse wird diese vom Benutzer eingegeben Bibliothek löst eine benutzerdefinierte Ausnahme aus nw :: invalid_ip aus std :: runtime_error . nw :: invalid_ip enthält ein was () beschreibt die Fehlermeldung, aber auch die incorrect_ip () Adresse angegeben.
Ich benutze auch std :: vector um Sockets zu speichern und benutze die checked at () Aufruf, um sicher auf Indizes zuzugreifen. Ich weiß das, wenn ich Rufen Sie at () für einen Wert außerhalb der Grenzen std :: out_of_range auf.
Ich weiß, dass auch andere Dinge geworfen werden können, aber ich weiß nicht, wie handle sie, oder was genau sie sein könnten.
Wenn ich einen nw :: invalid_ip Fehler erhalte, blende ich ein Modal mit einer Eingabe auf Box für den Benutzer mit der ungültigen IP-Adresse gefüllt, so dass sie bearbeiten können es und versuche es noch einmal.
Für std :: out_of_range Probleme reagiere ich, indem ich eine Integritätsprüfung durchführe auf den Sockeln und Fixieren der Vektor / Socket-Beziehung, die gefallen ist nicht synchron.
Für alle anderen std :: exception Probleme beende ich das Programm mit einem Fehlerprotokoll. Schließlich habe ich einen catch (...) , der "unbekannter Fehler" protokolliert. und beendet.
Es wäre schwierig, dies robust zu tun, wenn nur std :: string geworfen wird.
Hier ist ein grundlegendes Beispiel für einige Dinge, die in verschiedenen Fällen geworfen werden, damit Sie mit dem Abfangen von Ausnahmen spielen können.
Es ist eine berechtigte Frage, denn std::exception
enthält nur eine einzige Eigenschaft: what()
, a string
. Daher ist es verlockend, string
anstelle von exception
zu verwenden. Fakt ist aber, dass ein exception
kein string
ist. Wenn Sie eine Ausnahme so behandeln, als wäre sie nur ein string
, verlieren Sie die Fähigkeit, davon in spezialisierten Ausnahmeklassen abzuleiten, die mehr Eigenschaften bereitstellen würden.
Zum Beispiel werfen Sie heute string
s in Ihren eigenen Code. Morgen beschließen Sie, bestimmten Fällen wie einer Datenbankverbindungsausnahme weitere Eigenschaften hinzuzufügen. Sie können nicht einfach von string
ableiten, um diese Änderung vorzunehmen. Sie müssen eine neue Ausnahmeklasse schreiben und alle Ausnahmehandler für string
ändern. Die Verwendung von exception
ist eine Möglichkeit für Ausnahmehandler, nur die Daten zu verwenden, die sie interessieren, um Ausnahmen auszuwählen und auszuwählen, wenn sie diese behandeln müssen.
Auch wenn Sie nur string
-typisierte Ausnahmen werfen und behandeln, werden Sie alle Ausnahmen auslassen, die von einem Code stammen, der nicht Ihr eigener Code ist. Wenn diese Unterscheidung beabsichtigt ist, wird sie am besten beschrieben, indem eine allgemeine Ausnahmeklasse verwendet wird und nicht der generische Typ von string
.
exception
ist auch spezifischer als string
. Dies bedeutet, dass Bibliotheksentwickler Funktionen schreiben können, die Ausnahmen als Parameter akzeptieren, was klarer ist, als ein string
zu akzeptieren.
All dies ist im Wesentlichen kostenlos. Verwenden Sie einfach exception
anstelle von string
.
Nur weil in einem 6-zeiligen Spielzeugbeispiel etwas "gut funktioniert", bedeutet das nicht, dass es in echtem Code skalierbar oder wartbar ist.
Betrachten Sie diese Funktion:
%Vor% Dies könnte bad_alloc
auslösen, wenn Speicher für die Zeichenfolge nicht zugewiesen werden kann, oder bad_cast
ausgeben, wenn die Konvertierung fehlschlägt.
Ein Aufrufer dieser Funktion möchte möglicherweise den Fall der fehlgeschlagenen Konvertierung behandeln, der eine fehlerhafte Eingabe anzeigt, aber kein schwerwiegender Fehler ist, aber nicht mit dem Speichermangel umgehen möchte, da sie nichts dagegen tun können Lassen Sie die Ausnahme also den Stapel hochfahren. Dies ist beispielsweise in C ++ sehr einfach:
%Vor% Wenn Ausnahmen nur als std::string
ausgegeben würden, müsste der Code lauten:
Dies erfordert mehr zu implementierenden Code und beruht auf dem genauen Wortlaut der Ausnahmekette, die von der Compiler-Implementierung und der Definition von boost::lexical_cast
abhängen kann. Wenn jeder Teil der Ausnahmebehandlung im System Zeichenkettenvergleiche ausführen müsste, um zu entscheiden, ob der Fehler an diesem Punkt behandelt werden kann, wäre er unordentlich und nicht wartbar. Eine kleine Änderung an der Schreibweise einer Ausnahmebedingungsnachricht in einem Teil des Systems, das Ausnahmen auslöst, kann dazu führen, dass der Ausnahmebehandlungscode in einem anderen Teil des Systems nicht mehr funktioniert. Dies erzeugt eine enge Kopplung zwischen dem Ort eines Fehlers und jedes Bit des Fehlerbehandlungscodes im System. Einer der Vorteile der Verwendung von Ausnahmen besteht darin, dass die Fehlerbehandlung von der Hauptlogik getrennt werden kann. Diesen Vorteil verlieren Sie, wenn Sie Abhängigkeiten basierend auf Zeichenfolgenvergleichen im gesamten System erstellen.
Die Ausnahmebehandlung in C ++ fängt die Dinge ab, indem sie auf den Typ der Ausnahmebedingung abgleicht, es fängt nicht nach dem Wert einer Ausnahmebedingung ab, so dass es sinnvoll ist, Dinge verschiedener Typen zu werfen, um eine feinkörnige Handhabung zu ermöglichen. Dinge eines einzelnen String-Typs zu werfen und sie basierend auf dem Wert der Zeichenfolge zu behandeln, ist unordentlich, nicht portabel und schwieriger.
Später, wenn ich brauche, kann ich einfach meine eigenen "Ausnahme" -Typen erstellen. Also, warum sollte ich mich mit std :: exception beschäftigen?
Wenn Ihr Code nützlich und wiederverwendbar ist und ich ihn in einem Teil meines Systems verwenden möchte, muss ich die Ausnahmebehandlung hinzufügen, die alle Ihre leichten Typen abfängt? Warum sollte sich mein gesamtes System um die internen Details einer Bibliothek kümmern, auf die ein Bit des Systems angewiesen ist? Wenn Ihre benutzerdefinierten Ausnahmetypen von std::exception
abgeleitet sind, kann ich sie nach const std::exception&
abfangen, ohne die spezifischen Typen zu kennen oder zu pflegen.
Wenn Sie der einzige Benutzer der Klasse sind, können Sie std::exception
vermeiden (, wenn Sie die Standardbibliotheksausnahmen vermeiden möchten )
Aber wenn Ihre Klasse von anderen verwendet wird ( Programmierer ), wie würden sie die Ausnahme behandeln?
Wenn Ihre Klasse throws
a string
den Fehler beschreibt, würde das nicht helfen, da die Konsumenten Ihrer Klasse einen Standard bevorzugen würden (< em> fangen Sie ein Ausnahmeobjekt ab und fragen Sie, welche Methode ), um das exception
zu behandeln, anstatt eine Zeichenkette zu fangen ..
Sie können auch Ausnahmen abfangen, die von der Standardbibliothek ausgelöst werden, indem Sie ein exception
-Objekt abfangen
Sie können die Methode what
der Ausnahme Klasse überschreiben, um weitere Informationen über den Fehler zu erhalten.
Wow, ich bin überrascht, dass niemand das erwähnte:
Sie benötigen mehrere Arten von Ausnahmen, um sie unterscheiden zu können - einige Arten von Ausnahmen sollten behandelt werden, andere nicht.
Sie müssen eine gemeinsame Basisklasse haben, damit Sie eine vernünftige Nachricht für den Benutzer anzeigen können, ohne alle die Arten von Ausnahmen kennen zu müssen, die Ihr Programm möglicherweise haben könnte throw (was bei Verwendung externer Closed-Source-Bibliotheken nicht möglich ist).