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 goto
passt 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?
quelle
Antworten:
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
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::thread
oderstd::thread
) eine Ausnahme aus, um ordnungsgemäß beendet zu werden.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.
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
new
kann 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), einstd::list::push_back
Dosenwurf ...Ausnahmegarantie
Das erste, was Sie verstehen müssen, ist, dass Sie in der Lage sein müssen, die Ausnahmegarantie aller Ihrer Funktionen zu bewerten:
Beispiel für Code
Der folgende Code scheint korrektes C ++ zu sein, bietet jedoch in Wahrheit die Garantie "keine" und ist daher nicht korrekt:
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:
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:
Wir haben die Vorgänge neu angeordnet, indem wir zuerst
X
den richtigen Wert erstellt und eingestellt haben. Wenn eine Operation fehlschlägt,t
wird sie nicht geändert. Daher können die Operationen 1 bis 3 als "stark" betrachtet werden: Wenn etwas ausgelöst wird,t
wird es nicht geändert undX
leckt nicht, da es dem intelligenten Zeiger gehört.Dann erstellen wir eine Kopie
t2
vont
und bearbeiten diese Kopie von Operation 4 bis 7. Wenn etwas wirft,t2
wird es geändert, aber dannt
ist es immer noch das Original. Wir bieten weiterhin die starke Garantie.Dann tauschen wir
t
undt2
. Swap-Operationen sollten in C ++ nicht angezeigt werden. Hoffen wir also, dass der Swap, für den Sie geschrieben haben, nichtT
angezeigt 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
t
hat seinen Ausnahmewert. Wenn es fehlschlägt,t
hat 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:
swap()
Funktionen. Es sollte jedoch beachtet werden, dassstd::swap()
dies aufgrund der intern verwendeten Vorgänge fehlschlagen kannDie Standardeinstellung erstellt
std::swap
Kopien 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 - Operationvector
,deque
undlist
nicht werfen, während es für könnte ,map
wenn 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
Dionadar bezieht sich auf die folgende Zeile, die tatsächlich ein undefiniertes Verhalten aufweist.
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.
quelle
"finally" does exactly what you were trying to achieve
Natürlich. Und weil es einfach ist, spröden Code mit zu erstellenfinally
, wurde der Begriff "Try-with-Resource" schließlich in Java 7 eingeführt (dh 10 Jahre nach C #using
und 3 Jahrzehnte nach C ++ - Destruktoren). Das ist diese Sprödigkeit, die ich kritisiere. WasJust 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 #using
und Javatry
) hinzuzufügen .RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimes
Nein, das tut es nicht. Sie können intelligente Zeiger oder Dienstprogrammklassen verwenden, um Ressourcen zu "schützen".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:
Persönlich habe ich diese Artikel ziemlich spät entdeckt, so dass ein Großteil meines C ++ - Codes definitiv nicht ausnahmesicher ist.
quelle
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.
quelle
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.
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_ptr
für Speicher,boost::lock_guard
zum 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.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
/catch
blocks 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 ohnetry
/catch
Blöcke überall korrekt weiterzulaufen .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.
Ich habe im Laufe der Jahre einige Variationen ausprobiert, z. B. das Codieren von Zuständen in den oberen Bits (ala
HRESULT
s ) oder diesen schrecklichensetjmp() ... 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:
try
/catch
wenn Sie etwas über eine bestimmte Ausnahme tun könnennew
oderdelete
Code sehenstd::sprintf
,snprintf
und Arrays im Allgemeinen - Einsatzstd::ostringstream
für die Formatierung und ersetzen Arrays mitstd::vector
undstd::string
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 .
quelle
new
oderdelete
in Code sehen": Mit Raw meinen Sie wohl außerhalb eines Konstruktors oder Destruktors.delete
niemals außerhalb der Implementierung vontr1::shared_ptr
und dergleichen verwendet werden.new
kann verwendet werden, vorausgesetzt, seine Verwendung ist so etwas wietr1::shared_ptr<X> ptr(new X(arg, arg));
. Der wichtige Teil ist, dass das Ergebnis vonnew
direkt in einen verwalteten Zeiger geht. Die Seiteboost::shared_ptr
Best Practices beschreibt die besten.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 .
quelle
Nun, das habe ich bestimmt vor.
Ich bin sicher, dass meine mit Ausnahmen erstellten 24/7-Server rund um die Uhr ausgeführt werden und keinen Speicherverlust verursachen.
Es ist sehr schwierig, sicher zu sein, dass der Code korrekt ist. Normalerweise kann man nur nach Ergebnissen gehen
Nein. Die Verwendung von Ausnahmen ist sauberer und einfacher als alle Alternativen, die ich in den letzten 30 Jahren beim Programmieren verwendet habe.
quelle
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.
quelle
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.
quelle
noexcept
.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.
quelle
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.quelle
new(std::nothrow)
ist nicht genug. Übrigens ist es einfacher,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?
quelle
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.]
quelle
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.
quelle
f = new foo(); f->doSomething(); delete f;
Wenn die Methode doSomething eine Ausnahme auslöst, liegt ein Speicherverlust vor.