Wozu exception handling?



  • Es scheint mir, dass guter C++-Code Exception-Sicherheit impliziert. Es wird penibel vor allem in Konstruktoren darauf geachtet, dass alle Resourcen wieder frei gegeben werden, wenn irgendwo irgendwas ein throw macht.
    Wozu?
    Entweder ich kenne den Fehler, dann kann ich ihn lösen oder zumindest umgehen, sodass keine Exception geworfen wird, oder das Programm beendet sich mit einer Fehlermeldung. Wenn der Fehler nicht bekannt ist, dann kann man auch nicht korrekt damit umgehen, wenn doch, dann löst man ihn.

    Der einzige Fall, wo Exception-handling angebracht scheint, ist, wenn eine Bibliothek sehr throw-happy ist und bei Kleinigkeiten, die eigentlich keine Fehler (schon gar keine Exceptions) sind, Exceptions wirft. Das wiederspricht aber dem anfangs vorausgesetzten guten C++-Code.

    Wenn das Programm trotz Fehler immer weiter laufen soll, dann kann ich auch gleich Returnwerte nicht auswerten und mir die Exceptions sparen.

    Anders ausgedrückt: Wenn in einer Ausnahmesituation ein Resourcenleck entsteht ist das völlig in Ordnung. Wenn diese Situation so häufig vorkommt, dass die Resourcen knapp werden, dann war das keine Ausnahmesituation und Exceptions wurden falsch benutzt und das Programm funktioniert eh nicht, obwohl es kein Resourcenleck hat. In keinem Fall ist Exception-handling sinnvoll.

    Mir ist schon klar, dass ich irgendwas übersehe, ich hoffe es kann mich jemand erleuchten.



  • Och nicht schon wieder. Bitte nutze die Forensuche und lies die Argumentation!



  • Das Programm soll eben NICHT sofort abstürzen, sondern mit einer Fehlermeldung weiterlaufen, sonst bringen exceptions gar nichts.
    Außerdem gibt es zwei Arten, zum einen die nur für den Entwickler da sind und im Gegensatz zu normalen Abstürzen, die durch UB auch noch funktionieren oder im Extremfall sogar das System zum Absturz bringen könnten, haben sie noch eine Fehlerbeschreibung.
    Die anderen Exceptions sind gewollt, wenn z.B. eine Datei nicht geöffnet werden kann, und dann soll der ganze Einlesevorgang abgebrochen werden und an anderer Stelle (z.B. beim InputHandler des GUIs) wird die Fehlermeldung als Messagebox ausgegeben.

    nwp3 schrieb:

    Entweder ich kenne den Fehler, dann kann ich ihn lösen oder zumindest umgehen, sodass keine Exception geworfen wird, oder das Programm beendet sich mit einer Fehlermeldung. Wenn der Fehler nicht bekannt ist, dann kann man auch nicht korrekt damit umgehen, wenn doch, dann löst man ihn.

    Das stimmt nur teilweise, denn manchmal kann man Fehler nicht vorhersehen (z.B. beim einlesen von Dateien). An den anderen Stellen helfen Exceptions, den Fehler zu finden, indem die grundlegenden Konstrukte bei Fehlzugriff exceptions werfen und so informieren.
    Außerdem kann man während Entwicklung nicht immer absehen, in wie weit Parameter o.Ä. richtig sein können und eine Exception ist besser als ein stiller error.


  • Mod

    Marthog schrieb:

    Das Programm soll eben NICHT sofort abstürzen, sondern mit einer Fehlermeldung weiterlaufen, sonst bringen exceptions gar nichts.

    Es kann auch ganz ok sein, mit einer Exception einfach abzustürzen, wenn man sie nicht sinnvoll behandeln kann. Hauptsache ist, man bekommt den Fehler auf der Nutzerebene überhaupt mit, anstatt dass da irgendwo im Stillen UB erzeugt wird, dass dann fünfzehn Minuten später an völlig anderer Stelle zu einem kaum zu debuggenden Absturz führt. Die Exception führt somit, wie du auch selber schon schreibst, zu erheblicher Erleichterung bei der Fehlersuche, egal ob gefangen oder nicht*.

    *: Technisch gesehen wird die ungefangene Exception natürlich von der Laufzeitumgebung gefangen, die dann hoffentlich eine aussagekräftige Meldung macht, was da passiert ist. Genau das möchte man.



  • SeppJ schrieb:

    Marthog schrieb:

    Das Programm soll eben NICHT sofort abstürzen, sondern mit einer Fehlermeldung weiterlaufen, sonst bringen exceptions gar nichts.

    Es kann auch ganz ok sein, mit einer Exception einfach abzustürzen, wenn man sie nicht sinnvoll behandeln kann. Hauptsache ist, man bekommt den Fehler auf der Nutzerebene überhaupt mit, anstatt dass da irgendwo im Stillen UB erzeugt wird, dass dann fünfzehn Minuten später an völlig anderer Stelle zu einem kaum zu debuggenden Absturz führt.

    In der Theorie ist das vielleicht die sauberste Lösung, aber in der Praxis – speziell bei GUI-Anwendungen – ist es immer äußerst ärgerlich. In 97.8% aller Fälle hat eine Zugriffsverletzung triviale Ursachen und keine konsistenzschädigenden Folgen, und eine einfache Fehlermeldung reicht. Und selbst wenn du es für besser hältst, das Programm zu beenden, mach doch eine Meldung wie "Das Programm hat einen unbekannten Fehler festgestellt. Bitte speichern Sie Ihre Arbeit und starten Sie das Programm neu." Das erspart den Benutzern das Haareraufen.

    Edit: Die Premiumvariante ist wohl das, was Office macht. Die neueren Versionen sind eher schwer kaputtzukriegen, aber ich habe mal gesehen, wie OneNote einem Dozenten während der Vorlesung abstürzte. Da kam ein Dialog "Microsoft OneNote funktioniert nicht mehr", OneNote wurde beendet, automatisch neugestartet und war genau in demselben Dokument an derselben Stelle wie zuvor, kein Datenverlust. So sollte es sein.


  • Mod

    audacia schrieb:

    SeppJ schrieb:

    Marthog schrieb:

    Das Programm soll eben NICHT sofort abstürzen, sondern mit einer Fehlermeldung weiterlaufen, sonst bringen exceptions gar nichts.

    Es kann auch ganz ok sein, mit einer Exception einfach abzustürzen, wenn man sie nicht sinnvoll behandeln kann. Hauptsache ist, man bekommt den Fehler auf der Nutzerebene überhaupt mit, anstatt dass da irgendwo im Stillen UB erzeugt wird, dass dann fünfzehn Minuten später an völlig anderer Stelle zu einem kaum zu debuggenden Absturz führt.

    In der Theorie ist das vielleicht die sauberste Lösung, aber in der Praxis – speziell bei GUI-Anwendungen – ist es immer äußerst ärgerlich. In 97.8% aller Fälle hat eine Zugriffsverletzung triviale Ursachen und keine konsistenzschädigenden Folgen, und eine einfache Fehlermeldung reicht. Und selbst wenn du es für besser hältst, das Programm zu beenden, mach doch eine Meldung wie "Das Programm hat einen unbekannten Fehler festgestellt. Bitte speichern Sie Ihre Arbeit und starten Sie das Programm neu." Das erspart den Benutzern das Haareraufen.

    Nein, ich sage nicht, dass das immer beste Praxis ist. Dein Beispiel ist ein Paradebeispiel dafür, wo man es nicht so machen sollte. Aber ein kleines, feines Filterprogramm darf gerne abstürzen, wenn ein Systemcall eine cpu_on_fire_exception wirft.



  • Exception = Release-Mode-Assert.
    Das hatte ich gelesen, aber nicht geglaubt.
    Dass Exceptions sinnvoll sind steht außer Frage. Ich frage mich nur, wieso so viel Arbeit in Exception-Safety gesteckt wird.
    Fehler beim Einlesen von Dateien halte ich nicht für Ausnahmen, das ist ganz normal. Aber ich sehe, dass man hier eine Exception als Multireturn (return aus 3 Funktionen) missbrauchen kann.
    Die main in ein Try-Catch(...) zu wickeln finde ich auch sinnvoll, aber halt nur, um eine sinvolle Fehlerausgabe zu machen und noch in eine Logdatei Fehlerdetails auszugeben. Das fällt aber nicht in mein Verständnis von Exception-Handling und Exception-Safety (völlig egal, ob da Resourcen geleckt sind).



  • nwp3 schrieb:

    Wenn das Programm trotz Fehler immer weiter laufen soll, dann kann ich auch gleich Returnwerte nicht auswerten und mir die Exceptions sparen.

    Nein. Exceptions sind dazu da, Fehlerursache und -behandlung zu entkoppeln, und genau in diesem Fall ist das Multi-Return mit Stack-Unwinding, das Exceptions verursachen, auch sinnvoll.

    Sagen wir, der Benutzer hat im Dateiauswahl eine Datei ohne Schreibrechte ausgewählt. Drei Ebenen verschachtelt in deiner Laderoutine wird versucht, die Datei zu öffnen, was aber fehlschlägt. Was tut die Laderoutine nun? Eine Fehlermeldung anzeigen kann sie nicht, weil sie idealerweise dein Frontend (Terminal oder GUI) nicht kennt. Sie könnte Fehlercodes zurückgeben, die mußt du dann aber durch alle Ebenen durchreichen. Oder sie wirft eine Exception. Und die Message-Pump deiner GUI-Anwendung – dort ist bekannt, wie man Fehlermeldungen anzeigt – hat einen Exception-Handler (wie von SeppJ vorgeschlagen), fängt die Exception und gibt dem Benutzer Bescheid.



  • nwp3 schrieb:

    Fehler beim Einlesen von Dateien halte ich nicht für Ausnahmen, das ist ganz normal.

    Aber von einer Funktion zum Einlesen von Daten, wird erwartet, dass sie die Daten einliest. Tut sie das nicht, dann hat man eine Ausnahme, hier aber eine erwartbare.

    Bei Datei einlesen können Fehler beim Öffnen, beim Datein einlesen und beim Daten auswerten an sehr vielen Punkten auftreten, z.T. stark verschachtelt. Würde man jetzt immer Flags auswerten und jeweils wieder an den Ausrufer übergeben, entsteht unleserlicher Code, man vergisst möglicherweise das Auswerten und hat dadurch weitere Fehler und es läuft doch auf das Gleiche heraus, denn ein Fehler hierbei bedeutet meistens, dass die Datei nicht eingelesen werden kann oder wenn schon, fehlerhafte Daten enthalten könnte.
    Mit einer Exception kann man von jeder Stelle des Einlesens und Datenverarbeitens an das Ende springen und dort eine Fehlermeldung ausgeben.

    nwp3 schrieb:

    Aber ich sehe, dass man hier eine Exception als Multireturn (return aus 3 Funktionen) missbrauchen kann.

    Das sollte man aber nicht machen. Für solche Fälle gibt es tuples und variant types, bzw. man kann sich welche selber schreiben.
    Exceptions sind ein vielfaches langsamer und außerdem sieht es unübersichtlich aus, denn niemand erwartet das so.

    nwp3 schrieb:

    Die main in ein Try-Catch(...) zu wickeln finde ich auch sinnvoll, aber halt nur, um eine sinvolle Fehlerausgabe zu machen und noch in eine Logdatei Fehlerdetails auszugeben. Das fällt aber nicht in mein Verständnis von Exception-Handling und Exception-Safety (völlig egal, ob da Resourcen geleckt sind).

    Dafür kann man Exceptions nutzen, aber man kann auch zwischendurch erwartbare Fehler abfangen und das Programm gleich weiterlaufen lassen. Dabei will man natürlich nicht, dass memory leaks auftreten.


  • Mod

    Ich habe den Eindruck, dass dir der primäre Sinn von Exceptions nicht klar ist. Exceptions ermöglichen es, die Fehlerbehandlung an eine Stelle auszulagern, die die Kompetenz hat, mit dem Fehler umzugehen.
    Beispiel new: Das new ist eine low-level Operation, die schiefgehen kann, die von Benutzercode aufgerufen wird. Oftmals mit sehr, sehr vielen Schichten Nutzercode um das new herum. Wenn das new schiefgeht, wie soll das new wissen, was die richtige Reaktion ist? Es weiß schließlich nicht, in welcher Art von Programm es sich befindet. Darum schmeißt es eine Exception. Die propagiert dann so lange weiter, bis eine Stelle entscheiden kann, ob ein bad_alloc für diese Programm ein schwerwiegender Ausnahmefehler ist oder bloß eine Unannehmlichkeit. Soll das Programm noch irgendetwas sichern? Abstürzen? Ignorieren? All das kann das new nicht wissen, das ist Sache des Anwendungscodes, der weiß, was überhaupt die Anwendung ist 😉 .

    Damit das funktioniert, braucht der Anwendungscode natürlich Garantien darüber, was der benutzte Code im Falle einer Exception macht. Wenn eine Exception tief im Code ohnehin UB in den Zwischenschichten auslöst, kann der Anwendungscode auch nichts mehr machen, da der gesamte Programmzustand undefiniert ist. Oder er kann wissen, dass der Zwischencode schon selber die Exceptions behandelt und gar nichts mehr werfen kann (der Zwischencode ist dann selber der Anwendungscode aus Sicht der Funktion, die die Exception geworfen hat). Oder er kann wissen, dass das Programm in einem zwar fehlgeschlagenen, aber definierten Zustand ist und somit weitere Aktionen möglich sind und welche.

    Allgemein was Cleanup-Code und somit den Sinn von finally/RAII angeht: Es gibt auch andere Arten von Ressourcen als vom Betriebssystem verwalteten virtuellen Speicher. So manche davon müssen tatsächlich explizit von einem Programm freigegeben werden, da sie sonst auch nach Beenden des Programms noch belegt wären. Ein Beispiel für so etwas wäre eine Kommunikationsverbindung zu einem Server. Wenn man niemals "auflegt", dann denkt der Server bloß, man schicke gerade keine Daten, anstatt dass der Kommunikationspartner gar nicht mehr existiert.

    edit: Oh, zwei gleichlautende Antworten in der Zeit, wo ich dies schrieb. War zu erwarten, bei so viel Text :p . Dafür gibt's als Bonus diesen Link, falls er noch nicht bekannt sein sollte, wo ein weiteres Mal die gleichen Erklärungen drin stehen:
    http://www.c-plusplus.net/forum/219864



  • Ich muss auch sagen dass Exeptions sehr hilfreich sein können aber auf der anderen Seite auch eine Menge an Arbeit erzeugen und hin und wieder fühlt man sich sogar genötigt, recht unschönen Code zu schreiben damit das ganze Exeption save ist. Manchmal frage ich mich auch, ob es das wert ist.


  • Mod

    TNA schrieb:

    Ich muss auch sagen dass Exeptions sehr hilfreich sein können aber auf der anderen Seite auch eine Menge an Arbeit erzeugen und hin und wieder fühlt man sich sogar genötigt, recht unschönen Code zu schreiben damit das ganze Exeption save ist. Manchmal frage ich mich auch, ob es das wert ist.

    Wenn du durch Exceptions bereits viel Arbeit und unschönen Code erzeugst, wie sähe denn erst dann der gleiche Code mit klassischen Fehlercodes aus? 😮



  • Mein Eindruck ist, dass Ausnahme-sicherer Code in der Regel kürzer ist als welcher, der nicht sicher ist.
    Und im Gegensatz zu Java oder C# sieht man in C++ fast nie ein try , weil es den Destruktor gibt. Da der Destruktor auch ohne Ausnahmen ein unbezahlbares Hilfsmittel gegen Lecks aller Art ist, sollte man ihn unbedingt benutzen. Mit etwas Sachverstand bekommt man dann Ausnahmesicherheit kostenlos dazu.
    Ausnahmen sind das sinnvollste Mittel zur Fehlerbehandlung in C++. Rückgabewerte wie in C sind viel zu umständlich und fehleranfällig. Schaut euch mal gute C-Programme an, die viele Ressourcen verwalten. Da entfällt schnell mal die Hälfte aller Statements auf Fehlerbehandlung.



  • TyRoXx schrieb:

    Mein Eindruck ist, dass Ausnahme-sicherer Code in der Regel kürzer ist als welcher, der nicht sicher ist.

    foo(std::unique_ptr<IrgendEineKlasse>(new IrgendEineKlasse))
    

    ist nunmal länger als

    foo(new IrgendEineKlasse);
    


  • Marthog schrieb:

    TyRoXx schrieb:

    Mein Eindruck ist, dass Ausnahme-sicherer Code in der Regel kürzer ist als welcher, der nicht sicher ist.

    foo(std::unique_ptr<IrgendEineKlasse>(new IrgendEineKlasse))
    

    ist nunmal länger als

    foo(new IrgendEineKlasse);
    

    Das ist kein Java. Die Leute wissen, dass es in C++ delete gibt.
    Da wird also noch an mindestens einer Stelle so ein Müll stehen wie:

    if (p)
    {
    	delete p;
    	p = NULL;
    }
    

    Sogar eher an mehreren Stellen pro new .
    Das ist dann deutlich länger als richtiges C++.

    Außerdem ist die Länge eines einzigen Ausdrucks nicht so entscheidend. Ich meinte eher die Länge von Funktionen bzw die Anzahl der Statements.



  • Abgesehen davon:

    Marthog schrieb:

    foo(std::unique_ptr<IrgendEineKlasse>(new IrgendEineKlasse))
    

    Ist nicht exception-safe... 😉

    Edit: Ok, in dem konkreten Beispiel isses in der Regel wohl ok.

    foo(std::unique_ptr<IrgendEineKlasse>(new IrgendEineKlasse), std::unique_ptr<IrgendEineAndereKlasse>(new IrgendEineAndereKlasse))
    

    wär aber schon nimmer ok...



  • Marthog schrieb:

    foo(std::unique_ptr<IrgendEineKlasse>(new IrgendEineKlasse))
    

    Darum wird C++14 auch std::make_unique() haben.

    foo(std::make_unique<IrgendEineKlasse>())
    

    Ist exceptionsicher, vermeidet die doppelte Benennung des Typen und bringt new endgültig aus dem Anwendercode.



  • nwp3 schrieb:

    Anders ausgedrückt: Wenn in einer Ausnahmesituation ein Resourcenleck entsteht ist das völlig in Ordnung. Wenn diese Situation so häufig vorkommt, dass die Resourcen knapp werden, dann war das keine Ausnahmesituation und Exceptions wurden falsch benutzt und (...)

    Falsch.
    Begründung: z.B. das was audacia geschrieben hat. Bzw. wenn du 1-2 Minuten googelst wirst du "Milliarden und Abermilliarden" von Diskussionen zu dem Thema finden, wo die ganzen alten Argumente durchgekaut werden.

    Bzw. warum Exceptions genau nicht nur für das da sind was du beschreibst, beantwortest du dir ja selbst, indem du feststellst dass sie dann sinnlos wären.

    Exception != Fatal-Error. Exception = Ausnahmesituation. Ausnahmesituation = nicht der Regelfall. Nicht der Regelfall != kommt nie vor. Case closed.



  • chaos7568568 schrieb:

    ihr HU*ENSO*HNE!!!

    was zum geier bedeutet UB!!!

    Unter-belichted



  • Undefined Behaviour - Der Standard lässt offen, was dann passiert und die konkrete Implementierung darf selber entscheiden. Oft wird dabei nach Geschwindigkeit entschieden und UB ist in vielen Fällen zu meiden, weil es sein kann, dass alles funktioniert, aber in 10 Jahren und auf einem anderen Betriebssystem irgendwo ein kleiner Fehler auftritt, der alles zum Zusammenbrechen bringt.


Anmelden zum Antworten