Schreiben Sie (wirklich) ausnahmesicheren Code? [geschlossen]

312

Exception Handling (EH) scheint der aktuelle Standard zu sein, und wenn ich im Internet suche, kann ich keine neuen Ideen oder Methoden finden, die versuchen, ihn zu verbessern oder zu ersetzen (nun, es gibt einige Variationen, aber nichts Neues).

Obwohl die meisten Menschen , es zu ignorieren scheinen oder es einfach zu akzeptieren, EH hat einige große Nachteile: Ausnahmen sind an den Code unsichtbar und es schafft viele, viele mögliche Ausstiegspunkte. Joel über Software hat einen Artikel darüber geschrieben . Der Vergleich mit gotopasst perfekt, es hat mich wieder über EH nachdenken lassen.

Ich versuche, EH zu vermeiden und verwende nur Rückgabewerte, Rückrufe oder was auch immer zum Zweck passt. Aber wenn Sie zuverlässigen Code schreiben müssen, können Sie EH heutzutage einfach nicht ignorieren : Es beginnt mit dem new, was eine Ausnahme auslösen kann, anstatt nur 0 zurückzugeben (wie früher). Dies macht ungefähr jede Zeile von C ++ - Code anfällig für eine Ausnahme. Und dann werfen mehr Stellen im C ++ - Grundcode Ausnahmen aus ... std lib macht das und so weiter.

Das fühlt sich an, als würde man auf wackeligen Böden laufen . Jetzt müssen wir uns um Ausnahmen kümmern!

Aber es ist schwer, es ist wirklich schwer. Sie müssen lernen, ausnahmesicheren Code zu schreiben, und selbst wenn Sie Erfahrung damit haben, müssen Sie dennoch jede einzelne Codezeile überprüfen, um die Sicherheit zu gewährleisten! Oder Sie setzen überall Try / Catch-Blöcke, wodurch der Code unübersichtlich wird, bis er unleserlich wird.

EH ersetzte den alten sauberen deterministischen Ansatz (Rückgabewerte ..), der nur wenige, aber verständliche und leicht lösbare Nachteile hatte, durch einen Ansatz, der viele mögliche Austrittspunkte in Ihrem Code schafft, und wenn Sie anfangen, Code zu schreiben, der Ausnahmen abfängt (was Sie irgendwann dazu gezwungen werden), dann erstellt es sogar eine Vielzahl von Pfaden durch Ihren Code (Code in den catch-Blöcken, denken Sie an ein Serverprogramm, in dem Sie andere Protokollierungsfunktionen als std :: cerr .. benötigen). EH hat Vorteile, aber darum geht es nicht.

Meine eigentlichen Fragen:

  • Schreiben Sie wirklich ausnahmesicheren Code?
  • Sind Sie sicher, dass Ihr letzter "produktionsbereiter" Code ausnahmesicher ist?
  • Können Sie sich überhaupt sicher sein, dass es so ist?
  • Kennen und / oder verwenden Sie Alternativen, die funktionieren?
Frunsi
quelle
44
"EH hat den alten sauberen deterministischen Ansatz ersetzt (Rückgabewerte ..)" Was? Ausnahmen sind genauso deterministisch wie Rückkehrcodes. Es ist nichts Zufälliges an ihnen.
Rlbond
2
EH ist seit langer Zeit der Standard (seit Ada sogar!)
12
Ist "EH" eine etablierte Abkürzung für die Ausnahmebehandlung? Ich habe es noch nie gesehen.
Thomas Padron-McCarthy
3
Newtons Gesetze gelten heute für die meisten Dinge. Die Tatsache, dass sie seit Jahrhunderten eine Vielzahl von Phänomenen vorhergesagt haben, ist von Bedeutung.
David Thornley
4
@frunsi: Heh, tut mir leid, dass ich dich verwirrt habe! Aber ich denke, dass ein seltsames und ungeklärtes Akronym oder eine Abkürzung die Menschen mehr als gar nicht entmutigen wird.
Thomas Padron-McCarthy

Antworten:

520

Ihre Frage stellt die Behauptung auf, dass "das Schreiben von ausnahmesicherem Code sehr schwierig ist". Ich werde zuerst Ihre Fragen beantworten und dann die versteckte Frage dahinter beantworten.

Fragen beantworten

Schreiben Sie wirklich ausnahmesicheren Code?

Natürlich tue ich das.

Dies ist der Grund, warum Java für mich als C ++ - Programmierer viel an Attraktivität verloren hat (mangelnde RAII-Semantik), aber ich schweife ab: Dies ist eine C ++ - Frage.

Dies ist in der Tat erforderlich, wenn Sie mit STL- oder Boost-Code arbeiten müssen. Beispielsweise lösen C ++ - Threads ( boost::threadoder std::thread) eine Ausnahme aus, um ordnungsgemäß beendet zu werden.

Sind Sie sicher, dass Ihr letzter "produktionsbereiter" Code ausnahmesicher ist?

Können Sie sich überhaupt sicher sein, dass es so ist?

Das Schreiben von ausnahmesicherem Code ist wie das Schreiben von fehlerfreiem Code.

Sie können nicht 100% sicher sein, dass Ihr Code ausnahmesicher ist. Aber dann streben Sie danach, indem Sie bekannte Muster verwenden und bekannte Anti-Muster vermeiden.

Kennen und / oder verwenden Sie Alternativen, die funktionieren?

In C ++ gibt es keine praktikablen Alternativen (dh Sie müssen zu C zurückkehren und C ++ - Bibliotheken sowie externe Überraschungen wie Windows SEH vermeiden).

Ausnahmesicheren Code schreiben

Um einen ausnahmesicheren Code zu schreiben, müssen Sie zuerst wissen, wie hoch die Ausnahmesicherheit für jede Anweisung ist, die Sie schreiben.

Zum Beispiel newkann a eine Ausnahme auslösen, aber das Zuweisen eines integrierten (z. B. eines int oder eines Zeigers) schlägt nicht fehl. Ein Tausch wird niemals scheitern (schreiben Sie niemals einen Wurf-Tausch), ein std::list::push_backDosenwurf ...

Ausnahmegarantie

Das erste, was Sie verstehen müssen, ist, dass Sie in der Lage sein müssen, die Ausnahmegarantie aller Ihrer Funktionen zu bewerten:

  1. keine : Ihr Code sollte das niemals bieten. Dieser Code leckt alles und bricht bei der ersten ausgelösten Ausnahme zusammen.
  2. Grundlegend : Dies ist die Garantie, die Sie zumindest anbieten müssen, dh wenn eine Ausnahme ausgelöst wird, gehen keine Ressourcen verloren und alle Objekte sind noch vollständig
  3. stark : Die Verarbeitung ist entweder erfolgreich oder löst eine Ausnahme aus. Wenn sie jedoch ausgelöst wird, befinden sich die Daten in demselben Zustand, als ob die Verarbeitung überhaupt nicht gestartet worden wäre (dies gibt C ++ eine Transaktionsleistung).
  4. nothrow / nofail : Die Verarbeitung wird erfolgreich sein.

Beispiel für Code

Der folgende Code scheint korrektes C ++ zu sein, bietet jedoch in Wahrheit die Garantie "keine" und ist daher nicht korrekt:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

Ich schreibe meinen gesamten Code unter Berücksichtigung dieser Art von Analyse.

Die niedrigste angebotene Garantie ist einfach, aber dann macht die Reihenfolge jedes Befehls die gesamte Funktion "keine", denn wenn 3. wirft, leckt x.

Das erste, was zu tun wäre, wäre, die Funktion "grundlegend" zu machen, dh x in einen intelligenten Zeiger zu setzen, bis es sicher im Besitz der Liste ist:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Jetzt bietet unser Code eine "grundlegende" Garantie. Es wird nichts auslaufen und alle Objekte befinden sich in einem korrekten Zustand. Aber wir könnten mehr bieten, das heißt die starke Garantie. Hier kann es teuer werden, und deshalb ist nicht der gesamte C ++ - Code stark. Lass es uns versuchen:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Wir haben die Vorgänge neu angeordnet, indem wir zuerst Xden richtigen Wert erstellt und eingestellt haben. Wenn eine Operation fehlschlägt, twird sie nicht geändert. Daher können die Operationen 1 bis 3 als "stark" betrachtet werden: Wenn etwas ausgelöst wird, twird es nicht geändert und Xleckt nicht, da es dem intelligenten Zeiger gehört.

Dann erstellen wir eine Kopie t2von tund bearbeiten diese Kopie von Operation 4 bis 7. Wenn etwas wirft, t2wird es geändert, aber dann tist es immer noch das Original. Wir bieten weiterhin die starke Garantie.

Dann tauschen wir tund t2. Swap-Operationen sollten in C ++ nicht angezeigt werden. Hoffen wir also, dass der Swap, für den Sie geschrieben haben, nicht Tangezeigt wird (wenn dies nicht der Fall ist, schreiben Sie ihn neu, damit er nicht angezeigt wird).

Wenn wir also das Ende der Funktion erreichen, war alles erfolgreich (kein Rückgabetyp erforderlich) und that seinen Ausnahmewert. Wenn es fehlschlägt, that es immer noch seinen ursprünglichen Wert.

Das Anbieten der starken Garantie kann sehr kostspielig sein. Versuchen Sie also nicht, die starke Garantie für Ihren gesamten Code anzubieten. Wenn Sie dies jedoch ohne Kosten tun können (und C ++ - Inlining und andere Optimierungen können den gesamten oben genannten Code kostenlos machen). , dann tu es. Der Funktionsbenutzer wird es Ihnen danken.

Fazit

Es ist eine Gewohnheit, ausnahmesicheren Code zu schreiben. Sie müssen die Garantie bewerten, die von jeder Anweisung angeboten wird, die Sie verwenden, und dann müssen Sie die Garantie bewerten, die von einer Liste von Anweisungen angeboten wird.

Natürlich wird der C ++ - Compiler die Garantie nicht sichern (in meinem Code biete ich die Garantie als @ warning doxygen-Tag an), was ein bisschen traurig ist, aber es sollte Sie nicht davon abhalten, ausnahmesicheren Code zu schreiben.

Normaler Fehler gegen Fehler

Wie kann ein Programmierer garantieren, dass eine No-Fail-Funktion immer erfolgreich ist? Immerhin könnte die Funktion einen Fehler haben.

Das ist wahr. Die Ausnahmegarantien sollen durch fehlerfreien Code angeboten werden. In jeder Sprache setzt der Aufruf einer Funktion jedoch voraus, dass die Funktion fehlerfrei ist. Kein vernünftiger Code schützt sich vor der Möglichkeit eines Fehlers. Schreiben Sie den Code so gut Sie können, und bieten Sie dann die Garantie mit der Annahme, dass er fehlerfrei ist. Und wenn es einen Fehler gibt, korrigieren Sie ihn.

Ausnahmen sind außergewöhnliche Verarbeitungsfehler, keine Codefehler.

Letzte Worte

Die Frage ist nun "Lohnt sich das?".

Natürlich ist es das. Eine "nothrow / no-fail" -Funktion zu haben, die weiß, dass die Funktion nicht fehlschlägt, ist ein großer Segen. Das Gleiche gilt für eine "starke" Funktion, mit der Sie Code mit Transaktionssemantik wie Datenbanken mit Commit / Rollback-Funktionen schreiben können, wobei das Commit die normale Ausführung des Codes ist und Ausnahmen das Rollback auslösen.

Dann ist das "Basic" die geringste Garantie, die Sie anbieten sollten. C ++ ist dort eine sehr starke Sprache mit ihren Bereichen, die es Ihnen ermöglicht, Ressourcenlecks zu vermeiden (etwas, das ein Garbage Collector nur schwer für die Datenbank-, Verbindungs- oder Dateihandles anbieten kann).

Also, soweit ich es sehe, es ist es wert.

Edit 2010-01-29: Über nicht werfende Swap

nobar hat einen Kommentar abgegeben, der meiner Meinung nach sehr relevant ist, da er Teil von "Wie schreibt man ausnahmesicheren Code?" ist:

  • [ich] Ein Tausch wird niemals scheitern (schreibe nicht einmal einen Wurf-Tausch)
  • [nobar] Dies ist eine gute Empfehlung für benutzerdefinierte swap()Funktionen. Es sollte jedoch beachtet werden, dass std::swap()dies aufgrund der intern verwendeten Vorgänge fehlschlagen kann

Die Standardeinstellung erstellt std::swapKopien und Zuweisungen, die für einige Objekte ausgelöst werden können. Daher könnte der Standard-Swap entweder für Ihre Klassen oder sogar für STL-Klassen verwendet werden. Soweit die C ++ Standard betrifft, für die Swap - Operation vector, dequeund listnicht werfen, während es für könnte , mapwenn der Vergleich Funktor auf Kopie Bau werfen (siehe Die Programmiersprache C ++, Special Edition, Anhang E, E.4.3 .Swap ).

Bei der Visual C ++ 2008-Implementierung des Vektortauschs wird der Vektortausch nicht ausgelöst, wenn die beiden Vektoren denselben Zuweiser haben (dh im Normalfall), sondern Kopien erstellen, wenn sie unterschiedliche Zuweiser haben. Und so gehe ich davon aus, dass es in diesem letzten Fall werfen könnte.

Der ursprüngliche Text lautet also weiterhin: Schreiben Sie niemals einen Wurf-Tausch, aber der Kommentar von Nobar muss beachtet werden: Stellen Sie sicher, dass die Objekte, die Sie tauschen, einen nicht-Wurf-Tausch haben.

Edit 2011-11-06: Interessanter Artikel

Dave Abrahams , der uns die grundlegenden / starken / nicht garantierten Garantien gab , beschrieb in einem Artikel seine Erfahrungen mit der Sicherheit der STL-Ausnahme:

http://www.boost.org/community/exception_safety.html

Schauen Sie sich den 7. Punkt an (automatisierte Tests für Ausnahmesicherheit), an dem er sich auf automatisierte Komponententests stützt, um sicherzustellen, dass jeder Fall getestet wird. Ich denke, dieser Teil ist eine ausgezeichnete Antwort auf die Frage des Autors " Kannst du dir überhaupt sicher sein, dass es so ist? ".

Edit 2013-05-31: Kommentar von dionadar

t.integer += 1;ist ohne die Garantie, dass kein Überlauf auftritt, NICHT ausnahmesicher und kann tatsächlich UB technisch aufrufen! (Der signierte Überlauf ist UB: C ++ 11 5/4. "Wenn das Ergebnis während der Auswertung eines Ausdrucks nicht mathematisch definiert ist oder nicht im Bereich der darstellbaren Werte für seinen Typ liegt, ist das Verhalten undefiniert.") Beachten Sie, dass das Vorzeichen nicht angegeben ist Ganzzahlen laufen nicht über, sondern führen ihre Berechnungen in einer Äquivalenzklasse Modulo 2 ^ # Bits durch.

Dionadar bezieht sich auf die folgende Zeile, die tatsächlich ein undefiniertes Verhalten aufweist.

   t.integer += 1 ;                 // 1. nothrow/nofail

Die Lösung besteht darin, std::numeric_limits<T>::max()vor dem Hinzufügen zu überprüfen, ob die Ganzzahl bereits ihren Maximalwert erreicht hat (using ).

Mein Fehler würde in den Abschnitt "Normaler Fehler vs. Fehler" gehen, dh ein Fehler. Dies macht die Argumentation nicht ungültig und bedeutet nicht, dass der ausnahmesichere Code nutzlos ist, weil er nicht zu erreichen ist. Sie können sich nicht vor dem Ausschalten des Computers, Compiler-Fehlern oder sogar Ihren Fehlern oder anderen Fehlern schützen. Sie können keine Perfektion erreichen, aber Sie können versuchen, so nah wie möglich zu kommen.

Ich habe den Code unter Berücksichtigung von Dionadars Kommentar korrigiert.

paercebal
quelle
6
Vielen Dank! Ich hoffte immer noch auf etwas anderes. Aber ich akzeptiere Ihre Antwort für das Festhalten an C ++ und für Ihre ausführliche Erklärung. Sie haben sogar meine "Dies macht etwa jede Zeile von C ++ - Code anfällig für eine Ausnahme" -offense widerlegt. Diese zeilenweise Analyse macht doch Sinn ... Ich muss darüber nachdenken.
Frunsi
8
"Ein Tausch wird niemals scheitern". Dies ist eine gute Empfehlung für benutzerdefinierte swap () - Funktionen. Es ist jedoch zu beachten, dass std :: swap () aufgrund der intern verwendeten Operationen fehlschlagen kann.
Nobar
7
@Mehrdad :: "finally" does exactly what you were trying to achieveNatürlich. Und weil es einfach ist, spröden Code mit zu erstellen finally, wurde der Begriff "Try-with-Resource" schließlich in Java 7 eingeführt (dh 10 Jahre nach C # usingund 3 Jahrzehnte nach C ++ - Destruktoren). Das ist diese Sprödigkeit, die ich kritisiere. Was Just because [finally] doesn't match your taste (RAII doesn't match mine, [...]) doesn't mean it's "failing": In dieser Hinsicht stimmt die Branche nicht mit Ihrem Geschmack überein, da Garbage Collected-Sprachen dazu neigen, RAII-inspirierte Aussagen (C # usingund Java try) hinzuzufügen .
Paercebal
5
@Mehrdad :: RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimesNein, das tut es nicht. Sie können intelligente Zeiger oder Dienstprogrammklassen verwenden, um Ressourcen zu "schützen".
Paercebal
5
@Mehrdad: "Es zwingt Sie, einen Wrapper für etwas zu erstellen, für das Sie möglicherweise keinen Wrapper benötigen" - Wenn Sie in RAII-Form codieren, benötigen Sie keine Wrapper: Die Ressource ist das Objekt, und die Lebensdauer der Objekte ist die Lebensdauer der Ressourcen. Da ich jedoch aus einer C ++ - Welt komme, habe ich derzeit Probleme mit einem Java-Projekt. Im Vergleich zu RAII in C ++ ist dieses "manuelle Ressourcenmanagement" in Java eine Nachricht! Meine Meinung, aber Java hat vor langer Zeit "automatische Speicherverwaltung" gegen "automatische Ressourcenverwaltung" getauscht.
Frunsi
32

Beim Schreiben von ausnahmesicherem Code in C ++ geht es nicht so sehr darum, viele try {} catch {} -Blöcke zu verwenden. Es geht darum zu dokumentieren, welche Garantien Ihr Code bietet.

Ich empfehle, Herb Sutters Guru Of The Week- Reihe zu lesen , insbesondere die Raten 59, 60 und 61.

Zusammenfassend können Sie drei Stufen der Ausnahmesicherheit bereitstellen:

  • Grundlegend: Wenn Ihr Code eine Ausnahme auslöst, verliert Ihr Code keine Ressourcen und Objekte bleiben zerstörbar.
  • Stark: Wenn Ihr Code eine Ausnahme auslöst, bleibt der Status der Anwendung unverändert.
  • Kein Wurf: Ihr Code löst niemals Ausnahmen aus.

Persönlich habe ich diese Artikel ziemlich spät entdeckt, so dass ein Großteil meines C ++ - Codes definitiv nicht ausnahmesicher ist.

Joh
quelle
1
Sein Buch "Exceptional C ++" ist auch eine gute Lektüre. Aber ich versuche immer noch, das Konzept von EH in Frage zu stellen ...
Frunsi
8
+1 OP verbindet die Behandlung von Ausnahmen (Abfangen) mit der Ausnahmesicherheit (normalerweise mehr über RAII)
jk.
Ich vermute, dass sehr wenig Produktions-C ++ - Code ausnahmesicher ist.
Raedwald
18

Einige von uns verwenden seit über 20 Jahren Ausnahmen. PL / I hat sie zum Beispiel. Die Annahme, dass es sich um eine neue und gefährliche Technologie handelt, erscheint mir fraglich.

bmargulies
quelle
Bitte versteh mich nicht falsch, ich frage (oder versuche es) EH. Und vor allem C ++ EH. Und ich suche nach Alternativen. Vielleicht muss ich es akzeptieren (und ich werde es tun, wenn es der einzige Weg ist), aber ich denke, es könnte bessere Alternativen geben. Es ist nicht so, dass ich denke, dass das Konzept neu ist, aber ja, ich denke, es kann gefährlicher sein als die explizite Fehlerbehandlung mit Rückkehrcodes ...
Frunsi
1
Wenn es dir nicht gefällt, benutze es nicht. Setzen Sie Try-Blöcke um Dinge, die Sie aufrufen müssen, die werfen können, und erfinden Sie den alten Fehlercode neu. Sie leiden unter den Problemen, die in einigen Fällen tolerierbar sind.
Bmargulies
Ok, perfekt, ich werde nur EH und Fehlercodes verwenden und damit leben. Ich bin ein Trottel, ich hätte alleine zu dieser Lösung kommen sollen! ;)
Frunsi
17

Zunächst (wie Neil sagte) ist SEH das strukturierte Ausnahmeverfahren von Microsoft. Es ähnelt der Ausnahmeverarbeitung in C ++, ist jedoch nicht mit dieser identisch. Tatsächlich müssen Sie die C ++ - Ausnahmebehandlung aktivieren, wenn Sie dies in Visual Studio möchten - das Standardverhalten garantiert nicht, dass lokale Objekte in allen Fällen zerstört werden! In beiden Fällen ist die Ausnahmebehandlung nicht wirklich schwieriger, sondern nur anders .

Nun zu Ihren eigentlichen Fragen.

Schreiben Sie wirklich ausnahmesicheren Code?

Ja. Ich bemühe mich in allen Fällen um ausnahmesicheren Code. Ich evangelisiere mit RAII-Techniken für den Zugriff auf Ressourcen (z. B. boost::shared_ptrfür Speicher, boost::lock_guardzum Sperren). Im Allgemeinen erleichtert die konsequente Verwendung von RAII- und Scope-Guarding- Techniken das Schreiben von ausnahmesicherem Code erheblich. Der Trick besteht darin, zu lernen, was existiert und wie man es anwendet.

Sind Sie sicher, dass Ihr letzter "produktionsbereiter" Code ausnahmesicher ist?

Nein, es ist so sicher wie es ist. Ich kann sagen, dass ich in mehreren Jahren mit 24/7-Aktivitäten keinen Prozessfehler aufgrund einer Ausnahme gesehen habe. Ich erwarte keinen perfekten Code, nur gut geschriebenen Code. Zusätzlich zur Gewährleistung der Ausnahmesicherheit gewährleisten die oben genannten Techniken die Korrektheit auf eine Weise, die mit try/ catchblocks nahezu unmöglich zu erreichen ist . Wenn Sie alles in Ihrem obersten Kontrollbereich (Thread, Prozess usw.) abfangen, können Sie sicher sein, dass Sie trotz Ausnahmen ( meistens ) weiterhin ausgeführt werden. Dieselben Techniken helfen Ihnen auch dabei, trotz Ausnahmen ohne try/ catchBlöcke überall korrekt weiterzulaufen .

Können Sie sich überhaupt sicher sein, dass es so ist?

Ja. Sie können durch ein gründliches Code-Audit sicher sein, aber niemand tut das wirklich, oder? Regelmäßige Codeüberprüfungen und sorgfältige Entwickler tragen jedoch wesentlich dazu bei.

Kennen und / oder verwenden Sie Alternativen, die funktionieren?

Ich habe im Laufe der Jahre einige Variationen ausprobiert, z. B. das Codieren von Zuständen in den oberen Bits (ala HRESULTs ) oder diesen schrecklichen setjmp() ... longjmp()Hack. Beide brechen in der Praxis auf völlig unterschiedliche Weise zusammen.


Wenn Sie sich angewöhnen, einige Techniken anzuwenden und sorgfältig darüber nachzudenken, wo Sie als Reaktion auf eine Ausnahme tatsächlich etwas tun können, erhalten Sie am Ende einen sehr lesbaren Code, der ausnahmesicher ist. Sie können dies zusammenfassen, indem Sie die folgenden Regeln befolgen:

  • Sie wollen nur , um zu sehen try/ catchwenn Sie etwas über eine bestimmte Ausnahme tun können
  • Sie möchten fast nie einen Rohcode newoder deleteCode sehen
  • Eschew std::sprintf, snprintfund Arrays im Allgemeinen - Einsatz std::ostringstreamfür die Formatierung und ersetzen Arrays mit std::vectorundstd::string
  • Suchen Sie im Zweifelsfall nach Funktionen in Boost oder STL, bevor Sie Ihre eigenen rollen

Ich kann nur empfehlen, dass Sie lernen, wie man Ausnahmen richtig verwendet und Ergebniscodes vergisst, wenn Sie in C ++ schreiben möchten. Wenn Sie Ausnahmen vermeiden möchten, sollten Sie in Betracht ziehen, in einer anderen Sprache zu schreiben, die diese entweder nicht enthält oder sicher macht . Wenn Sie wirklich lernen möchten, wie man C ++ voll ausnutzt, lesen Sie einige Bücher von Herb Sutter , Nicolai Josuttis und Scott Meyers .

D. Shawley
quelle
"Das Standardverhalten garantiert nicht, dass lokale Objekte in allen Fällen zerstört werden." Sie sagen, dass die Standardeinstellung des Visual Studio C ++ - Compilers Code erzeugt, der angesichts von Ausnahmen falsch ist . Ist das wirklich so?
Raedwald
"Sie wollen fast nie einen Raw newoder deletein Code sehen": Mit Raw meinen Sie wohl außerhalb eines Konstruktors oder Destruktors.
Raedwald
@Raedwald - re: VC ++: Die VS2005-Edition von VC ++ zerstört keine lokalen Objekte, wenn eine SEH-Ausnahme ausgelöst wird. Lesen Sie die "C ++ - Ausnahmebehandlung aktivieren" . In VS2005 rufen SEH-Ausnahmen standardmäßig nicht die Destruktoren von C ++ - Objekten auf. Wenn Sie Win32-Funktionen oder etwas aufrufen, das in einer C-Interface-DLL definiert ist, müssen Sie sich darüber Gedanken machen, da diese eine SEH-Ausnahme auslösen können (und gelegentlich auch).
D. Shawley
@Raedwald: re: raw : sollte grundsätzlich deleteniemals außerhalb der Implementierung von tr1::shared_ptrund dergleichen verwendet werden. newkann verwendet werden, vorausgesetzt, seine Verwendung ist so etwas wie tr1::shared_ptr<X> ptr(new X(arg, arg));. Der wichtige Teil ist, dass das Ergebnis von newdirekt in einen verwalteten Zeiger geht. Die Seite boost::shared_ptrBest Practices beschreibt die besten.
D. Shawley
Warum verweisen Sie in Ihrem Regelwerk für die Ausnahmesicherheit auf std :: sprintf (et al.)? Diese dokumentieren nicht, dass sie irgendwelche Ausnahmen auslösen
mabraham
10

Es ist nicht möglich, ausnahmesicheren Code unter der Annahme zu schreiben, dass "jede Zeile werfen kann". Das Design von ausnahmesicherem Code hängt entscheidend von bestimmten Verträgen / Garantien ab, die Sie in Ihrem Code erwarten, beobachten, befolgen und implementieren sollen. Es ist absolut notwendig, Code zu haben, der garantiert niemals wirft. Es gibt andere Arten von Ausnahmegarantien.

Mit anderen Worten, ist Ausnahme-sichere Code zu schaffen in hohem Maße eine Frage des Programmentwurfes nicht nur eine Frage der Ebene Codierung .

Ameise
quelle
8
  • Schreiben Sie wirklich ausnahmesicheren Code?

Nun, das habe ich bestimmt vor.

  • Sind Sie sicher, dass Ihr letzter "produktionsbereiter" Code ausnahmesicher ist?

Ich bin sicher, dass meine mit Ausnahmen erstellten 24/7-Server rund um die Uhr ausgeführt werden und keinen Speicherverlust verursachen.

  • Können Sie sich überhaupt sicher sein, dass es so ist?

Es ist sehr schwierig, sicher zu sein, dass der Code korrekt ist. Normalerweise kann man nur nach Ergebnissen gehen

  • Kennen und / oder verwenden Sie Alternativen, die funktionieren?

Nein. Die Verwendung von Ausnahmen ist sauberer und einfacher als alle Alternativen, die ich in den letzten 30 Jahren beim Programmieren verwendet habe.

anon
quelle
30
Diese Antwort hat keinen Wert.
Matt Joiner
6
@MattJoiner Dann hat die Frage keinen Wert.
Miles Rout
5

Abgesehen von der Verwirrung zwischen SEH- und C ++ - Ausnahmen müssen Sie sich bewusst sein, dass Ausnahmen jederzeit ausgelöst werden können, und Ihren Code in diesem Sinne schreiben. Das Bedürfnis nach Ausnahmesicherheit ist hauptsächlich der Grund für die Verwendung von RAII, intelligenten Zeigern und anderen modernen C ++ - Techniken.

Wenn Sie den etablierten Mustern folgen, ist das Schreiben von ausnahmesicherem Code nicht besonders schwierig. Tatsächlich ist es einfacher als das Schreiben von Code, der Fehlerrückgaben in allen Fällen ordnungsgemäß behandelt.

Mark Bessey
quelle
3

EH ist im Allgemeinen gut. Die Implementierung von C ++ ist jedoch nicht sehr benutzerfreundlich, da es sehr schwer zu sagen ist, wie gut Ihre Ausnahmebehandlung ist. Java zum Beispiel macht dies einfach. Der Compiler schlägt in der Regel fehl, wenn Sie mögliche Ausnahmen nicht behandeln.

Mr. Boy
quelle
2
Es ist viel besser mit vernünftiger Verwendung von noexcept.
Einpoklum
2

Ich arbeite sehr gerne mit Eclipse und Java (neu in Java), da es Fehler im Editor auslöst, wenn Sie einen EH-Handler vermissen. Das macht es viel schwieriger zu vergessen, eine Ausnahme zu behandeln ...

Außerdem werden mit den IDE-Tools automatisch der try / catch-Block oder ein anderer catch-Block hinzugefügt.

Crowe T. Robot
quelle
5
Das ist der Unterschied zwischen aktivierten (Java) und nicht aktivierten (c ++) Ausnahmen (Java hat auch einige davon). Der Vorteil von geprüften Ausnahmen ist das, was Sie geschrieben haben, aber dort hat es seine eigenen Nachteile. Google für die unterschiedlichen Ansätze und die unterschiedlichen Probleme, die sie haben.
David Rodríguez - Dribeas
2

Einige von uns bevorzugen Sprachen wie Java, die uns zwingen, alle von Methoden ausgelösten Ausnahmen zu deklarieren, anstatt sie wie in C ++ und C # unsichtbar zu machen.

Bei ordnungsgemäßer Ausführung sind Ausnahmen den Fehlerrückgabecodes überlegen, wenn Sie aus keinem anderen Grund Fehler manuell in der Aufrufkette verbreiten müssen.

Abgesehen davon sollte die Programmierung von API-Bibliotheken auf niedriger Ebene wahrscheinlich die Ausnahmebehandlung vermeiden und sich an Fehlerrückgabecodes halten.

Ich habe die Erfahrung gemacht, dass es schwierig ist, sauberen Code für die Ausnahmebehandlung in C ++ zu schreiben. Am Ende benutze ich new(nothrow)viel.

David R Tribble
quelle
4
Und Sie meiden auch den größten Teil der Standardbibliothek? Verwenden new(std::nothrow)ist nicht genug. Übrigens ist es einfacher,
ausnahmesicheren
3
Die Java-geprüfte Ausnahmebedienbarkeit ist stark übertrieben. Nicht-Java-Sprachen werden NICHT als Erfolg gewertet. Aus diesem Grund wird die Anweisung "throw" in C ++ jetzt als veraltet angesehen, und C # hat nie ernsthaft daran gedacht, sie zu implementieren (dies war eine Entwurfsentscheidung). Für Java könnte das folgende Dokument aufschlussreich sein: googletesting.blogspot.com/2009/09/…
paercebal
2
Ich habe die Erfahrung gemacht, dass das Schreiben von ausnahmesicherem Code in C ++ nicht so schwierig ist und im Allgemeinen zu saubererem Code führt. Natürlich muss man lernen, es zu tun.
David Thornley
2

Ich versuche mein Bestes, um ausnahmesicheren Code zu schreiben, ja.

Das heißt, ich achte darauf, welche Linien werfen können. Nicht jeder kann, und es ist von entscheidender Bedeutung, dies zu berücksichtigen. Der Schlüssel ist wirklich, über die im Standard definierten Ausnahmegarantien nachzudenken und Ihren Code so zu gestalten, dass er erfüllt wird.

Kann diese Operation geschrieben werden, um die Garantie für starke Ausnahmen zu bieten? Muss ich mich mit dem Grundlegenden zufrieden geben? Welche Zeilen können Ausnahmen auslösen und wie kann ich sicherstellen, dass sie das Objekt nicht beschädigen, wenn sie dies tun?

jalf
quelle
2
  • Schreiben Sie wirklich ausnahmesicheren Code? [Das gibt es nicht. Ausnahmen sind ein Papierschild für Fehler, es sei denn, Sie haben eine verwaltete Umgebung. Dies gilt für die ersten drei Fragen.]

  • Kennen und / oder verwenden Sie Alternativen, die funktionieren? [Alternative zu was? Das Problem hierbei ist, dass die tatsächlichen Fehler nicht vom normalen Programmbetrieb getrennt werden. Wenn es sich um einen normalen Programmbetrieb handelt (dh eine Datei wurde nicht gefunden), handelt es sich nicht wirklich um eine Fehlerbehandlung. Wenn es sich um einen tatsächlichen Fehler handelt, gibt es keine Möglichkeit, ihn zu behandeln, oder es handelt sich nicht um einen tatsächlichen Fehler. Ihr Ziel hier ist es, herauszufinden, was schief gelaufen ist, und entweder die Tabelle zu stoppen und einen Fehler zu protokollieren, den Treiber für Ihren Toaster neu zu starten oder einfach zu beten, dass der Jetfighter auch dann weiterfliegen kann, wenn seine Software fehlerhaft ist, und auf das Beste zu hoffen.]

Charles Eli Käse
quelle
0

Viele (ich würde sogar sagen die meisten) Leute tun es.

Was bei Ausnahmen wirklich wichtig ist, ist, dass das Ergebnis absolut sicher ist und sich gut verhält, wenn Sie keinen Bearbeitungscode schreiben. Zu panisch, aber sicher.

Sie müssen aktiv Fehler in Handlern machen, um etwas Unsicheres zu erhalten, und nur catch (...) {} wird mit dem Ignorieren von Fehlercode verglichen.

ima
quelle
4
Nicht wahr. Es ist sehr einfach, Code zu schreiben, der nicht ausnahmesicher ist. Beispiel: f = new foo(); f->doSomething(); delete f;Wenn die Methode doSomething eine Ausnahme auslöst, liegt ein Speicherverlust vor.
Kristopher Johnson
1
Es wäre egal, wann Ihr Programm endet, oder? Um die Ausführung fortzusetzen, müssen Sie Ausnahmen aktiv verschlucken. Nun, es gibt einige spezielle Fälle, in denen eine Beendigung ohne Bereinigung immer noch nicht akzeptabel ist, aber solche Situationen erfordern besondere Sorgfalt in jeder Programmiersprache und jedem Programmierstil.
Ima
Sie können Ausnahmen weder in C ++ noch in verwaltetem Code ignorieren (und keinen Bearbeitungscode schreiben). Es wird unsicher sein und sich nicht gut benehmen. Außer vielleicht einem Spielzeugcode.
Frunsi
1
Wenn Sie Ausnahmen im Anwendungscode ignorieren, verhält sich das Programm möglicherweise immer noch NICHT gut, wenn externe Ressourcen beteiligt sind. Das Betriebssystem kümmert sich zwar um das Schließen von Dateihandles, Sperren, Sockets usw. Es wird jedoch nicht alles gehandhabt, z. B. können beim Schreiben unnötige Dateien zurückbleiben oder Dateien beschädigt werden. Wenn Sie Ausnahmen ignorieren, haben Sie ein Problem in Java. In C ++ können Sie mit RAII arbeiten (aber wenn Sie die RAII-Technik verwenden, verwenden Sie sie höchstwahrscheinlich, weil Sie sich für Ausnahmen
interessieren
3
Bitte verdrehen Sie meine Worte nicht. Ich schrieb "wenn Sie keinen Handhabungscode schreiben" - und genau das meinte ich. Um Ausnahmen zu ignorieren, müssen Sie Code schreiben.
Ima