Wann und wie soll ich die Ausnahmebehandlung verwenden?


82

Ich lese über Ausnahmebehandlung. Ich habe einige Informationen zur Ausnahmebehandlung erhalten, habe aber einige Fragen:

  1. Wann eine Ausnahme auslösen?
  2. Können wir anstelle einer Ausnahme einen Rückgabewert verwenden, um den Fehler anzuzeigen?
  3. Wenn ich alle meine Funktionen durch Try-Catch-Blöcke schütze, verringert dies nicht die Leistung?
  4. Wann wird die Ausnahmebehandlung verwendet?
  5. Ich habe ein Projekt gesehen, in dem jede Funktion in diesem Projekt einen Try-Catch-Block enthielt (dh Code innerhalb der gesamten Funktion ist von einem Try-Catch-Block umgeben). Ist das eine gute Praxis?
  6. Was ist der Unterschied zwischen try-catch und __try __except?


Sie müssen spezifischere Fragen stellen als "Wann eine Ausnahme auslösen soll". Sie lösen eine Ausnahme aus, wenn etwas Außergewöhnliches passiert.
Falmarri

Ich habe darüber relativ zu PHP geschrieben, aber ich denke, dass so ziemlich alles auf C ++ anwendbar ist. Schauen Sie sich den Blog-Beitrag an .
Ircmaxell

Antworten:


95

Hier ist eine ziemlich umfassende Anleitung zu Ausnahmen, die ich für ein Muss halte:

Ausnahmen und Fehlerbehandlung - C ++ FAQ oder C ++ FAQ lite

Als Faustregel gilt, dass Sie eine Ausnahme auslösen müssen, wenn Ihr Programm eine externe Person identifizieren kannProblem, das die Ausführung verhindert. Wenn Sie Daten vom Server empfangen und diese Daten ungültig sind, lösen Sie eine Ausnahme aus. Nicht genügend Speicherplatz? Eine Ausnahme auslösen. Kosmische Strahlung hindert Sie daran, die Datenbank abzufragen? Eine Ausnahme auslösen. Wenn Sie jedoch ungültige Daten aus Ihrem eigenen Programm erhalten, lösen Sie keine Ausnahme aus. Wenn Ihr Problem von Ihrem eigenen schlechten Code herrührt, ist es besser, ASSERTs zu verwenden, um sich dagegen zu schützen. Die Ausnahmebehandlung ist erforderlich, um Probleme zu identifizieren, die das Programm nicht behandeln kann, und um sie über den Benutzer zu informieren, da der Benutzer sie behandeln kann. Aber Fehler in Ihrem Programm können vom Benutzer nicht behandelt werden, sodass ein Programmabsturz nicht viel weniger aussagt als "Der Wert von answer_to_life_and_universe_and_everything ist nicht 42! Dies sollte niemals passieren !!!! 11" Ausnahme.

Fangen Sie eine Ausnahme ab, in der Sie etwas Nützliches tun können, z. B. ein Meldungsfeld anzeigen. Ich ziehe es vor, eine Ausnahme einmal in einer Funktion abzufangen, die irgendwie Benutzereingaben verarbeitet. Zum Beispiel drückt der Benutzer die Taste "Alle Hunams vernichten" und in der Funktion "AnnihilateAllHunamsClicked ()" gibt es einen try ... catch-Block, um "Ich kann nicht" zu sagen. Obwohl die Vernichtung von hunamkind eine komplexe Operation ist, bei der Dutzende und Dutzende von Funktionen aufgerufen werden müssen, gibt es nur einen Versuch ... catch, denn für einen Benutzer ist es eine atomare Operation - ein Knopfdruck. Ausnahmeprüfungen in jeder Funktion sind redundant und hässlich.

Außerdem kann ich nicht genug empfehlen, sich mit RAII vertraut zu machen - das heißt, um sicherzustellen, dass alle initialisierten Daten automatisch zerstört werden. Dies kann erreicht werden, indem so viel wie möglich auf dem Stapel initialisiert wird. Wenn Sie etwas auf dem Heap initialisieren müssen, verwenden Sie eine Art intelligenten Zeiger. Alles, was auf dem Stapel initialisiert wurde, wird automatisch zerstört, wenn eine Ausnahme ausgelöst wird. Wenn Sie dumme Zeiger im C-Stil verwenden, besteht die Gefahr eines Speicherverlusts, wenn eine Ausnahme ausgelöst wird, da niemand sie bei einer Ausnahme bereinigen kann (Sie können Zeiger im C-Stil als Mitglieder Ihrer Klasse verwenden, stellen Sie jedoch sicher, dass dies der Fall ist im Destruktor erledigt).


Vielen Dank für Ihre wertvollen Beiträge. > "Ausnahmebehandlung ist erforderlich, um Probleme zu identifizieren, die das Programm nicht behandeln kann, und um sie über den Benutzer zu informieren, weil der Benutzer sie behandeln kann." Anstatt eine Ausnahme auszulösen, kann ich den Fehlerwert zurückgeben und in der aufrufenden Funktion nach dem Fehler suchen und zeigen Sie eine Nachricht an, die dem Benutzer hilft, damit umzugehen.
Umesha MS

Schauen Sie sich die FAQ noch einmal an, insbesondere die dritten und vierten Codefragmente in diesem Abschnitt: parashift.com/c++-faq-lite/exceptions.html#faq-17.2 Ausnahmen sind großartig, da sie Ihre Fehler aus jeder Tiefe auftauchen lassen of call stack ... Es ist wie wenn Fehlercodes Bathyspheres sind, Ausnahmen sind U-Boote :)
Septagram

2
@Septagram "Wenn Ihr Problem von Ihrem eigenen fehlerhaften Code herrührt, ist es besser, ASSERTs zu verwenden, um sich dagegen zu schützen.": Mit einer Zusicherung können Sie nicht mit Aufgaben fortfahren, die nicht auf dem fehlerhaften Zweig beruhen (nur weil Ihr Programm dies hat) Ein Fehler in einer Funktion bedeutet nicht, dass er keine anderen nützlichen Dinge tun kann. Sie können keine benutzerdefinierten Logger verwenden. Es umgeht die Destruktoren, von denen Sie durch die Verwendung von RAII profitieren (möchten Sie, dass Ihre Dateipuffer geleert werden, bevor das Programm beendet wird? Überspringen Sie diese Destruktoren fclosedann lieber nicht !). Sie erhalten keine vollständige Stapelverfolgung.
Dämonenfrühling

Etwas zu sagen über RAII und "dumme Zeiger". Nur weil Sie einen "dummen Zeiger" verwenden, heißt das nicht, dass Sie RAII nicht folgen. Denken Sie daran, dass Sie nur dann, wenn Sie Systemressourcen zuweisen, sicherstellen müssen, dass diese freigegeben werden. Ein Zeiger ist einfach eine Variable auf dem Stapel, die eine Speicheradresse enthält. Die Objektzuweisung und -initialisierung ist nicht erforderlich, um einen "dummen Zeiger" zu verwenden. Ein "dummer Zeiger" kann einfach die Adresse eines Objekts enthalten, das an anderer Stelle zugewiesen ist, und ist vollkommen in Ordnung zu verwenden. (Fortsetzung im nächsten Kommentar)
SeanRamey

Möglicherweise haben Sie ein Renderer-Objekt, das die Adresse des Anzeigeobjekts in einem Zeiger speichert, um Dinge auf die Anzeige zu zeichnen. Sie können jedoch keinen intelligenten Zeiger verwenden, da ein intelligenter Zeiger das Anzeigeobjekt zerstört, wenn Sie das Renderer-Objekt zerstören , was du nicht passieren willst. In diesem Fall sind entweder "dummer Zeiger" oder eine Referenz die einzigen Möglichkeiten.
SeanRamey

12

Ausnahmen sind unter verschiedenen Umständen nützlich.

Erstens gibt es einige Funktionen, bei denen die Kosten für die Berechnung der Vorbedingung so hoch sind, dass es besser ist, nur die Berechnung durchzuführen und mit einer Ausnahme abzubrechen, wenn festgestellt wird, dass die Vorbedingung nicht erfüllt ist. Zum Beispiel können Sie eine singuläre Matrix nicht invertieren. Um jedoch zu berechnen, ob sie singulär ist, berechnen Sie die Determinante, die sehr teuer ist: Möglicherweise muss sie ohnehin innerhalb der Funktion durchgeführt werden. Versuchen Sie also einfach, die Matrix und den Bericht zu invertieren Ein Fehler, wenn Sie keine Ausnahme auslösen können. Dies ist grundsätzlich eine Ausnahme als negative Vorbedingung .

Dann gibt es andere Fälle, in denen Ihr Code bereits komplex ist und es schwierig ist, Fehlerinformationen über die Aufrufkette zu übertragen. Dies liegt zum Teil daran, dass C und C ++ fehlerhafte Datenstrukturmodelle haben: Es gibt andere, bessere Möglichkeiten, aber C ++ unterstützt sie nicht (z. B. die Verwendung von Monaden in Haskell). Diese Verwendung ist im Grunde genommen nicht die Mühe, es richtig zu machen, also werde ich eine Ausnahme werfen : Es ist nicht der richtige Weg, aber es ist praktisch.

Dann gibt es die Hauptverwendung von Ausnahmen: um zu melden, wenn externe Voraussetzungen oder Invarianten, wie z. B. ausreichende Ressourcen wie Speicher oder Speicherplatz, nicht verfügbar sind. In diesem Fall beenden Sie normalerweise das Programm oder einen wichtigen Unterabschnitt davon. Die Ausnahme ist eine gute Möglichkeit, Informationen über das Problem zu übertragen. C ++ - Ausnahmen wurden entwickelt, um Fehler zu melden, die verhindern, dass das Programm fortgesetzt wird .

Es ist bekannt , dass das in den meisten modernen Sprachen, einschließlich C ++, verwendete Ausnahmebehandlungsmodell fehlerhaft ist . Es ist viel zu mächtig. Theoretiker haben jetzt bessere Modelle entwickelt als das völlig offene Modell "Alles werfen" und "Vielleicht und vielleicht nicht fangen". Darüber hinaus war es keine gute Idee, Typinformationen zur Klassifizierung von Ausnahmen zu verwenden.

Das Beste, was Sie tun können, ist , Ausnahmen sparsam zu werfen, wenn ein tatsächlicher Fehler vorliegt und wenn es keine andere Möglichkeit gibt, damit umzugehen und Ausnahmen so nahe wie möglich am Wurfpunkt zu fangen .


15
Könnten Sie bitte einige Links / Verweise für "Ausnahmebehandlung ist bekanntermaßen fehlerhaft" und "Theoretiker haben jetzt bessere Modelle entwickelt" hinzufügen?
Juraj Blaho

1
Eine wichtige Sache, die beim Abfangen von Ausnahmen häufig bekannt sein muss, über die die meisten Ausnahmen jedoch keine nützlichen Daten liefern, ist, ob der Code, der die Ausnahme ausgelöst hat, Auswirkungen auf den Status des Systems hat. Beschäftigt sich eines der Modelle der Theoretiker damit?
Supercat

@JurajBlaho Ich habe erfolglos versucht, die Links / Referenzen zu finden, über die Sie sprechen. Können Sie seine Antwort bearbeiten, um sie am Ende hinzuzufügen?
ForceMagic

Ich stimme Beispiel 1 und 3 zu. Ich denke jedoch, dass Sie, wenn der Code zu komplex ist, sodass Sie darüber nachdenken, eine Ausnahme auszulösen, möglicherweise ein Refactoring benötigen würden. Assert sind auch eine gute (noch bessere) Möglichkeit, um zu überprüfen, ob der Code ordnungsgemäß ausgeführt wird und tatsächlich günstiger als eine Ausnahme ist. Ich empfehle jedem, der sich für dieses Thema interessiert, zwei Kapitel (Assertive Programming und Wann sollten Ausnahmen verwendet werden) aus dem Buch: The Pragmatic Programmer.
ForceMagic

1
Keine Notwendigkeit für Links. Verwenden Sie Gehirne. Sie können eine Ausnahme auslösen, die nicht abgefangen wird. Das ist schlecht. Schlimmer als goto, da goto zumindest immer irgendwohin geht. Die statische Typisierung kann Ausnahmespezifikationen nicht bewältigen: Es gibt kein vernünftiges System, in dem polymorphe Funktionen eine Ausnahmespezifikation haben können, da sie von der jeweiligen Instanz der Typvariablen abhängt. Beachten Sie, dass diese aus dem neuen C ++ - Standard entfernt wurden. Das neue Modell heißt "begrenzte Fortsetzungen": en.wikipedia.org/wiki/Delimited_continuation
Yttrill

4

Wenn Ihr Problem von Ihrem eigenen schlechten Code herrührt, ist es besser, ASSERTs zu verwenden, um sich dagegen zu schützen. Die Ausnahmebehandlung ist erforderlich, um Probleme zu identifizieren, die das Programm nicht behandeln kann, und um sie über den Benutzer zu informieren, da der Benutzer sie behandeln kann. Aber Fehler in Ihrem Programm können vom Benutzer nicht behoben werden, sodass Programmabstürze nicht viel aussagen

Ich bin mit diesem Aspekt der akzeptierten Antwort nicht einverstanden . Eine Behauptung ist zweifellos nicht besser als eine Ausnahme auszulösen. Wenn Ausnahmen nur für Laufzeitfehler (oder "externe Probleme") geeignet waren, wozu std::logic_error?

Ein logischer Fehler ist fast per Definition die Art von Bedingung, die verhindert, dass ein Programm fortgesetzt wird. Wie kann das Programm fortgesetzt werden, wenn es sich um ein logisches Konstrukt handelt und eine Bedingung außerhalb des Bereichs dieser Logik auftritt? Sammeln Sie Eingaben, solange Sie können, und werfen Sie eine Ausnahme!

Es ist nicht so, dass es keinen Stand der Technik gibt. std::vector, um nur einen zu nennen, löst eine logische Fehlerausnahme aus, nämlich std::out_of_range. Wenn Sie die Standard - Bibliothek verwenden und haben keine Top-Level - Handler Standard Ausnahmen zu fangen - wenn auch nur zu nennen , was () und Ausgang (3) - dann Ihre Programme unterliegen einer abrupte stille, Kündigung.

Ein Assert-Makro ist eine viel schwächere Wache. Es gibt keine Wiederherstellung. Es sei denn, Sie führen keinen Debug-Build aus. In diesem Fall erfolgt keine Ausführung . Das Assert-Makro gehört zu einer Zeit, in der die Berechnung um 6 Größenordnungen langsamer war als heute. Wenn Sie sich die Mühe machen, auf Logikfehler zu testen, diesen Test jedoch nicht verwenden, wenn es darauf ankommt, sollten Sie in der Produktion viel Vertrauen in Ihren Code haben!

Die Standardbibliothek sieht logische Fehlerausnahmen vor und verwendet diese. Sie sind aus einem Grund da: weil logische Fehler auftreten und außergewöhnlich sind. Nur weil C Assertions enthält, ist dies kein Grund, sich auf einen solchen primitiven (und wohl nutzlosen) Mechanismus zu verlassen, wenn eine Ausnahme den Job so viel besser erledigt.


3

Am besten dafür lesen

In den letzten anderthalb Jahrzehnten wurde viel über die Behandlung von Ausnahmen gesprochen. Trotz eines allgemeinen Konsenses darüber, wie Ausnahmen richtig behandelt werden sollen, besteht weiterhin eine Kluft bei der Nutzung. Eine unsachgemäße Ausnahmebehandlung ist leicht zu erkennen, leicht zu vermeiden und eine einfache Qualitätsmetrik für Code (und Entwickler). Ich weiß, dass absolute Regeln engstirnig oder übertrieben wirken, aber generell sollten Sie try / catch nicht verwenden

http://codebetter.com/karlseguin/2010/01/25/don-t-use-try-catch/


3
Versucht dieser Artikel nicht wirklich zu sagen, benutze try/ nicht catch {}?
Craig McQueen

2

1. Eine Ausnahmeprüfung ist im Code enthalten, wenn die Möglichkeit besteht, dass eine Ausnahme als Ergebnis oder irgendwo zwischen dem Problem auftritt.

2.Verwenden Sie den Try-Catch-Block nur in den Fällen, in denen dies erforderlich ist. Die Verwendung jedes Try-Catch-Blocks trägt zu einer zusätzlichen Bedingungsprüfung bei, die die Optimierung des Codes sicherlich verringert.

3.Ich denke, _try_except ist ein gültiger Variablenname ....


3
Zu Ihrer Information, __try und __except gelten für das Structured Exception Handling (SEH) von Microsoft. msdn.microsoft.com/en-us/library/s58ftw19(v=vs.80).aspx
axw

0

Der grundlegende Unterschied ist:

  1. man macht Fehlerbehandlung für Sie.
  2. eine ist, dass du deine eigenen machst.

    • Zum Beispiel könnten Sie einen Ausdruck machen 0 divide error. Die Verwendung von try catch 1.hilft Ihnen, wenn ein Fehler aufgetreten ist. Oder du brauchst ein if a==0 then..in2.

    • Wenn Sie nicht versuchen, die Ausnahme abzufangen, denke ich nicht, dass sie schneller ist, sondern nur umgeht. Wenn sie errorauftritt, handelt es sich threwum einen externen Handler.

Sich selbst zu übergeben bedeutet, dass das Problem in vielen Fällen nicht weiter geht und dann einen Geschwindigkeitsvorteil hat, aber nicht immer.

Vorschlag: Behandeln Sie sich einfach, wenn es einfach und logisch ist.


-3

Viele C / C ++ - Puristen raten von Ausnahmen ab. Die Hauptkritikpunkte sind:

  1. Es ist langsam - Natürlich ist es nicht wirklich "langsam". Im Vergleich zu reinem c / c ++ ist der Aufwand jedoch recht hoch.
  2. Es führt Fehler ein - Wenn Sie Ausnahmen nicht richtig behandeln, können Sie den Bereinigungscode in der Funktion, die die Ausnahme auslöst, übersehen.

Überprüfen Sie stattdessen bei jedem Aufruf einer Funktion den Rückgabewert / Fehlercode.


15
Unsinn. Erstens sprechen wir von C ++ - es gibt kein "C / C ++", und natürlich wären C-only-Programmierer mit Ausnahmen unangenehm. "pure C / C ++" existiert nicht und Ausnahmen sind Teil von "pure C ++" - sie sind im Standard enthalten. Sie können fehlerhaften Code zur Behandlung von Ausnahmen schreiben, und Sie können fehlerhaften Fehlerrückgabecode oder fehlerhaften Fehlerzustandscode schreiben - es ist irreführend, Ausnahmen im Allgemeinen als fehleranfälliger zu charakterisieren. Sorry, aber dies ist einer der schlechtesten Ratschläge, die ich auf SO gesehen habe
Tony Delroy

1
Markieren Sie mich, wenn Sie möchten, aber wenn Sie einen C-Hintergrund haben, kann ich Ihnen sagen, dass ich und viele andere keine Ausnahmen verwenden.
Speedplane

4
1. Es ist nur langsam, wenn tatsächlich eine Ausnahme ausgelöst wird. Und Ausnahmen werden Ausnahmen genannt, weil sie nur in Ausnahmefällen ausgelöst werden. Ein Programm, das mehr als 9000 Ausnahmen pro Sekunde auslöst, macht es falsch. 2. Wenn Sie darauf achten, die Speicherverwaltung nicht mit reinem Neu-Löschen durchzuführen, wird die Bereinigung für Sie durchgeführt. Die Speicherverwaltung im C-Stil ist weitaus fehleranfälliger als Ausnahmen. 3. Durch Überprüfen des Rückgabewerts werden viele zusätzliche Codezeilen hinzugefügt, wodurch diese weniger lesbar und weniger wartbar sind.
Septagramm

3
1) abhängig vom Compiler, der möglicherweise nicht wahr ist. Und alle Ausnahmen fügen eine gute Menge an Byte-Code hinzu, wodurch die Größe der ausführbaren Dateien erhöht und die Geschwindigkeit verlangsamt wird. 2) Es wird ständig reines Neu-Löschen verwendet, nicht alles kann ein Stapelobjekt sein. 3) Das Überprüfen von Rückgabewerten kann mehr Codezeilen sein, ist aber wahrscheinlich besser zu warten. Sie sind vielleicht nicht einverstanden, aber es ist eine allgemein akzeptierte vernünftige Annahme, dass C ++ - Ausnahmen in vielen Fällen schlecht sind. Schauen Sie sich als Beispiel Embedded-C ++ an.
Speedplane

11 Downvotes und 8 Upvotes. Ich denke, es ist klar, dass diese Meinung zwar nicht den allgemeinen Konsens widerspiegelt, es aber ein großes Kontingent von Entwicklern gibt, die aus den von mir beschriebenen Gründen Ausnahmen befürchten.
Speedplane vor
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.