Exception Handling (Eure Konzepte, best practice,..)
-
Roger Wilco schrieb:
Hallo pumuckl und Danke für Deine Antwort!
pumuckl schrieb:
Die Frage ist meist zu allererst: "kann ich hier sinnvoll reagieren wenn eine Ausnahme kommt?" Denn wenn mans nicht kann reichts einfach, exceptionsicheren Code zu schreiben und die eventuell fliegenden Exceptions durchrauschen zu lassen bis zu einem Punkt, wo sie behandelt werden können.
Neben diesen wichtigen Aspekt, gibt es aber auch den Aspekt: "Ist es wichtig, festzuhalten, wo der Fehler aufgetreten ist?" Wenn ich später nur weiß, da ist irgendwo ein out-of-range aufgetreten, nützt mir die Information nur wenig (z.B. beim Loging). Daher dachte ich an den Ansatz, grundsätzlich alle Exceptions zu fangen, um dann die Information "Entstehungsort" hinzuzufügen. Dann kann im Log nachvollziehn, "WAS" passiert ist und "WO" es passiert ist.
An der Stelle, wo du die Exception behandelst, fängst du sie ja sowieso. Genau an der Stelle kannst du auch das Logging aufrufen und sagen "hier ist ein Problem aufgetreten". An einer tieferen Stelle einfach nur die "wo"-Info hinzuzufügen bringt dir nicht viel, denn wenn du die Exception auf einer höheren Ebene behandelst ist es nicht von Interesse, in welchem Teil der Unterroutinen sie aufgetreten ist, da du mit der Information nichts anfangen kannst (wenn es von Belang wäre ob die Exception in Unterfunktion f1 oder f2 aufgetreten ist, dann könntest du sie nach dem Aufruf von f1 bzw. f2 auch gleich behandeln).
pumuckl schrieb:
Die in deinem Code gezeigte Übersetzung von jeweils einer std::exception in eine andere exception halte ich für wenig sinnvoll, ...
Mir geht es bei dem Verfahren darum, dass ich die std::exceptions auf eine eigene Exception-Klassen-Hierachie (nach Fehlerart) umsetze. In dieser kann ich dann die Informationen "WAS" und "WO" getrennt ablegen, um sie bei einer Fehlerausgabe auch getrennt und einheitlich abrufen zu können. Zusätzlich habe ich pro Exceptionklasse noch einen allgemeinen fehlertypischen Text (z.B. "Konfigurationsfehler").
Wie gesagt, das "WO" gehört zum "WAS" dazu. Und der Typ der Exception besagt doch schon zu welchem allgemeinen Fehlertyp sie gehört, da ist der typische Fehlertext redundant. Sonst kommt am Ende an der Oberfläche eine Meldung wie "OutOfRange-Exception caught. Message: out_of_range exception (out of range)".
Was das Übersetzen angeht: wenn in deiner Routine foo eine outofrange oder filenotfound-exception auftaucht ists an höherer Stelle meist uninteressant was genau passiert ist, weil mans da dann eh kaum noch unterschiedlich behandeln kann -> eine fooFailureException reicht dann um die Fälle abzudecken.
-
Roger Wilco schrieb:
Shade Of Mine schrieb:
Du darfst nicht Klassenweise denken. Es gibt keine string-exception oder fstream-exception. Es gibt nur Fehlertypen: OutOfMemory, FileNOtFound, FileAccessDenied, etc. Die Exception Hierachie verlaeuft also parallel zu deinerKlasen Hierachie. Und inkluden muss man immer nur das was notwendig ist.
Ja, das denke ich auch. Nur sieht man oftmals Beispiele, wo die Exception-Deklaration mit in der Klassen-Headerdatei steht. Das liegt wahrscheinlich aber an dem "Vereinfachte aber teilweise unsinnige Code-Beispiele-Problem". Daher fragte ich, ob das gängig ist.
Also Exception-Klassenhierachie in mehrere eigene Header-Files und dann die verwendeten Exceptions inkludieren? Aus Gründen der Übersicht (oder Faulheit?) habe ich bisher alle in eine Datei getan und damit immer alle inkludiert.
Alle in einen Header zu packen ist unsinnig. Bei Exceptions die wirklich klassenspezifisch sind kann man sie durchaus mit in den Klassenheader packen, z.T. sogar eine Unterklasse daraus machen. In der Hinsicht muss ich Shade teilweise widersprechen: es gibt sowohl allgemeine Fehler wie die, die er angegeben hat, als auch kontextspezifische Fehler, die zu dem jeweiligen modul/der Klass gehören. Diese kontextspezifischen Exceptions gehören zum Interface der Klasse und können/sollten mit dieser zusammen geliefert werden. Sie können aber durchaus von allgemeineren Exceptions abgeleitet sein die nicht zu einer bestimmten Klasse gehören.
Beispielsweise könnte bei einer bestimmten Klasse Bar eine Funktion baz() nur mit bestimmten Werten aufgerufen werden dürfen - wird ein anderer Wert übergeben komt eine Bar::InvalidBazParamException, die abgeleitet ist von einem std::domain_error oder ähnlichem.
-
pumuckl schrieb:
An der Stelle, wo du die Exception behandelst, fängst du sie ja sowieso. Genau an der Stelle kannst du auch das Logging aufrufen und sagen "hier ist ein Problem aufgetreten". An einer tieferen Stelle einfach nur die "wo"-Info hinzuzufügen bringt dir nicht viel, denn wenn du die Exception auf einer höheren Ebene behandelst ist es nicht von Interesse, in welchem Teil der Unterroutinen sie aufgetreten ist, da du mit der Information nichts anfangen kannst (wenn es von Belang wäre ob die Exception in Unterfunktion f1 oder f2 aufgetreten ist, dann könntest du sie nach dem Aufruf von f1 bzw. f2 auch gleich behandeln).
Stimmt eigentlich. Das sagte auch schon Shade of mine, dass zentrales Logging ja nicht heißt, dass man an einer zentrale Stelle den logger aufruft. Ich war so von der Idee gefesselt, dass man mit Exceptions zentral (an einer Stelle im Code) Fehler behandeln und loggen kann. Aber wenn ich direkt nach der Entstehung logge, dann erübrigt sich die "Wo?"-Information natürlich.
pumuckl schrieb:
Was das Übersetzen angeht: wenn in deiner Routine foo eine outofrange oder filenotfound-exception auftaucht ists an höherer Stelle meist uninteressant was genau passiert ist, weil mans da dann eh kaum noch unterschiedlich behandeln kann -> eine fooFailureException reicht dann um die Fälle abzudecken.
Das wäre dann aber wieder eine Klassen-spezifische Exception, die nicht in die klassen-unabhängige Hierachie passt.
Aber setzten wir das mal beispielhaft um:
#include "MyException.h" class MethodeAException : public MyExceptionBase{...}; class MethodeBException : public MyExceptionBase{...}; void Klasse::MethodeA(){ try{ // viel STL-Code mit strings, streams, container etc... // viele potentielle std::exceptions sind hier verborgen } catch(std::exception& e){ myLog << e << "(Klasse::MethodeA)"; throw MethodeAException(); } } void Klasse::MethodeB(){ try{ // viel STL-Code mit strings, streams, container etc... // viele potentielle std::exceptions sind hier verborgen } catch(std::exception& e){ myLog << e << "(Klasse::MethodeB)"; throw MethodeBException(); } }#include "Klasse.h" ... void ZentraleKlasse::Hauptroutine(){ Klasse obj(); //wirft keine Exceptions try{ // jetzt aber... obj.MethodeA(); ... obj.MethodeB(); } catch(MyExceptionBase& e){ OnError(); } }
-
Oder mal anders gefragt:
Wie sieht bei Euch typisches Exception-Handling aus? Ein kleines Code-Beispiel wäre sehr hilfreich.
Natürlich variiert das je nach Anforderung und je nach Fall, aber ein sagen wir mal ein vereinfachter und "typischer" Fall von Exception-Handling in Euren Projekten (in dem viel mit STL gearbeitet wird z.B.).
-
Roger Wilco schrieb:
Wie sieht bei Euch typisches Exception-Handling aus? Ein kleines Code-Beispiel wäre sehr hilfreich.
In meinen Artikeln habe ich versucht einige Codes herzuzeigen.
Das Problem bei solchen Codes ist aber, du siehst nichts von den Exceptions - denn dank RAII räumst du automatisch alles auf und try/catch hat man nur an wenigen stellen wo man wirklich reagieren kann.guter exception sicherer code sieht nicht wirklich anders aus als anderer code, nur idR kürzer und klarer. der trick ist, dass man plötzlich keine fehlerbehandlung im code stehen hat.
-
Das mit dem Exception-sicheren Code ist mir auch klar (prinzipiell - Umsetzung ist ja nicht immer ganz einfach).
Aber ich habe da Problem, dass ich eigentlich grundsätzlich wissen möchte, wo ein Fehler aufgetreten ist. Das heißt für mich, dass ich eigentlich überall, wo ich Code einsetzte, der Exception werfen kann, dass ich viel try/catches habe, um einfach den Ort des Fehlers zu protokollieren.
Ich nutze sehr viel die Standard-Bibliothek und so ist mein ganzer Code voll von diesen try/catches, wobei ich die Exceptions entweder in meine Exception-Hierachie übersetzte oder einfach nur rethrow mache.
Das nervt mich ehrlich gesagt und macht den Code unübersichtlich. Daher wollte ich mal fragen, wie Ihr das so macht. Anscheinend interessiert Euch der Fehlerort aber i.d.R. nicht. Außerdem scheint Ihr i.d.R. die Exceptions der Standard-Bibliothek nicht in Eure Exception-Hierachie zu übersetzten, ist das richtig?
Exceptions fängt Ihr dort, wo Ihr reagieren könnt. Das ist klar. Aber das bedeutet, dass man dann Exceptions an solchen Stellen behandelt, wo man gar nicht weiß was genau oder wo es passiert ist. Z.B. ein bad_alloc eines std::strings oder ein ios_base::failure eines Streams irgendwo.
Mich würden ehrlich gesagt konkrete Beispiele (gerne auch nur prinzipieller Pseudo-Code) interessieren. Nicht so konstruierte main() ruft Funktion auf fängt Exception der Funktion. Das sind ja nur Beispiele, die die Funktionsweise an sich erklären, aber nicht der Einsatz in einem echten Projekt. Mein Ansatz scheint ja "falsch" zu sein. Nur wie mache ich es richtig?
-
Roger Wilco schrieb:
Das mit dem Exception-sicheren Code ist mir auch klar (prinzipiell - Umsetzung ist ja nicht immer ganz einfach).
Aber ich habe da Problem, dass ich eigentlich grundsätzlich wissen möchte, wo ein Fehler aufgetreten ist. Das heißt für mich, dass ich eigentlich überall, wo ich Code einsetzte, der Exception werfen kann, dass ich viel try/catches habe, um einfach den Ort des Fehlers zu protokollieren.
Was bringt dir das? Das Protokollieren von Fehlern direkt an Ort und Stelle ist im fertigen Programm absolut sinnfrei. Man denke nur an diese unsäglichen Windows-Fehlermeldungen "Unbekannter Fehler bei 0x056ad344 aufgetreten". Wenn im fertigen Programm etwas schief läuft ists dem Anwender herzlichst egal ob jetzt die Methode foo() oder bar() schiefgelaufen ist. Was ihn interessiert ist, dass beim Laden der Karte für sein Spiel ein Fehler aufgetreten ist.
Ich hoffe doch sehr, dass du Exceptions nicht benutzt um dein Programm während der Entwicklung zu debuggen. Dazu sind assertions da.Ich nutze sehr viel die Standard-Bibliothek und so ist mein ganzer Code voll von diesen try/catches, wobei ich die Exceptions entweder in meine Exception-Hierachie übersetzte oder einfach nur rethrow mache.
Was für zig Exceptions aus der Standardbibliothek erwartest du denn?
Außerdem scheint Ihr i.d.R. die Exceptions der Standard-Bibliothek nicht in Eure Exception-Hierachie zu übersetzten, ist das richtig?
Kommt auf die Exception an. Eine FileNotFound übersetze ich so, dass ich an der Stelle wo die entsprechende übergeordnete Routine die Datei lädt eine Fehlermeldugn ausgegeben wird, an welcher Stelle weiter unten im Programm ich den Stream versuche zu öffnen interessiert nicht.
Eine bad_alloc kann man andererseits kaum wirksam behandeln - da mache ich im Normalfall ein catch in der main, gebe noch eine Fehlermeldung aus, dass das Programm wegen Speichermangel in die Binsen gegangen ist und verlasse die Szene.Exceptions fängt Ihr dort, wo Ihr reagieren könnt. Das ist klar. Aber das bedeutet, dass man dann Exceptions an solchen Stellen behandelt, wo man gar nicht weiß was genau oder wo es passiert ist. Z.B. ein bad_alloc eines std::strings oder ein ios_base::failure eines Streams irgendwo.
Wen interessierts obs am String oder am Stream gelegen hat? In beiden Fällen kann ich nur die Schultern zucken, mir am Kopf kratzen, ggf. ein paar der Speicherfressenden Programme schließen und/oer mir zusätzlichen Speicher zulegen.
Mich würden ehrlich gesagt konkrete Beispiele (gerne auch nur prinzipieller Pseudo-Code) interessieren. Nicht so konstruierte main() ruft Funktion auf fängt Exception der Funktion. Das sind ja nur Beispiele, die die Funktionsweise an sich erklären, aber nicht der Einsatz in einem echten Projekt. Mein Ansatz scheint ja "falsch" zu sein. Nur wie mache ich es richtig?
Konkrete Beispiele siehst du oben. Dateizugriffsfehler behandle (und logge) ich dort wo ich die Datei öffne (was normalerweise ein paar Ebenen oberhalb des eigentlichen ftream::open ist). Bei Containerzugriffen gehe ich nicht davon aus dass ich Exceptions bekomme, sonst wäre die Programmlogik falsch an der Stelle. bad_alloc rauscht durch, weil da nichts mehr zu retten ist. Andere Exceptions fallen mir im Augenblick nicht ein.
-
pumuckl schrieb:
Was bringt dir das? Das Protokollieren von Fehlern direkt an Ort und Stelle ist im fertigen Programm absolut sinnfrei. Man denke nur an diese unsäglichen Windows-Fehlermeldungen "Unbekannter Fehler bei 0x056ad344 aufgetreten". Wenn im fertigen Programm etwas schief läuft ists dem Anwender herzlichst egal ob jetzt die Methode foo() oder bar() schiefgelaufen ist. Was ihn interessiert ist, dass beim Laden der Karte für sein Spiel ein Fehler aufgetreten ist.
Ich hoffe doch sehr, dass du Exceptions nicht benutzt um dein Programm während der Entwicklung zu debuggen. Dazu sind assertions da.Bei uns ist das so, dass die Software ein Log für uns hat (im fertigen Produkt aber nicht für den Kunden - zum Debuggen). Daher wollen wir wissen was (Fehlertyp) und wo (Klasse::Methode) passiert ist. Bisher läuft das über viele ifs, Returnwerte und fprintfs
Daher hatte ich jetzt mit Exceptions angefangen. Anhand der Exception war der Fehlertyp klar und in der Exception stand dann die Info Klasse::Methode. Geloggt wurde dann zentral.
Bisher wird das hier im alten C-Stil (Returnwerte, Ifs, fprintf,...) gemacht. Das versuche ich gerade 1:1 durch Exceptions zu lösen, was wohl mein falscher Ansatz ist. Aber hier kennt sich keiner mit Exceptions aus. Kommen mehr aus der C-Welt. Daher muss ich Euch löchern.Was für zig Exceptions aus der Standardbibliothek erwartest du denn?
Wenn etwas, dass ich verwende eine Exception werfen kann, muss ich sie doch (irgendwo) behandeln? Egal ob ich die nun "erwarte" oder nicht. ich erwarte eigentlich keine bad_allocs, reagiere aber trotzdem drauf. I.d.R. sind das bei mir immer 2-3 Exceptions, die auftreten könnten. Diese übersetzte ich dann in meine Exception.
an welcher Stelle weiter unten im Programm ich den Stream versuche zu öffnen interessiert nicht.
Aber wenn der Kunde dann kommt, sagt "Deine Software kann manchmal eine Datei nicht öffnen!" Wie findest Du den Fehler, woran es liegt - Angenommen es ist ein Bug in der Anwendung?
Wen interessierts obs am String oder am Stream gelegen hat?
Stimmt, das interessiert nicht. Aber der grundsätzliche Fehlertyp und der grobe Fehlerort interessiert schon.
Bei Containerzugriffen gehe ich nicht davon aus dass ich Exceptions bekomme, sonst wäre die Programmlogik falsch an der Stelle. bad_alloc rauscht durch, weil da nichts mehr zu retten ist. Andere Exceptions fallen mir im Augenblick nicht ein.
Das heißt Container-Fehler wie out_of_range behandelst Du gar nicht und lässt das Programm sich nur beenden? Was ist mit (internen/temporären) Streams? Fragst Du die Fehler-Bits ab oder nutzt Du die exceptions?
Ich möchte mich jedenfalls recht Herzlich für die Antworten und Eure Geduld bedanken!
-
Roger Wilco schrieb:
Wenn etwas, dass ich verwende eine Exception werfen kann, muss ich sie doch (irgendwo) behandeln? Egal ob ich die nun "erwarte" oder nicht. ich erwarte eigentlich keine bad_allocs, reagiere aber trotzdem drauf. I.d.R. sind das bei mir immer 2-3 Exceptions, die auftreten könnten. Diese übersetzte ich dann in meine Exception.
Du musst sie nicht dann behandeln, wenn sie auch laut Programmlogik auftauchen können, nicht wenn sie laut Interface der verwendeten Funktion auftauchen können. Wenn du z.B. durch die Programmlogik sicherstellst dass ein Containerzugriff gültig ist (z.B. index < size()), dann brauchst du keine Exception mehr abfangen.
an welcher Stelle weiter unten im Programm ich den Stream versuche zu öffnen interessiert nicht.
Aber wenn der Kunde dann kommt, sagt "Deine Software kann manchmal eine Datei nicht öffnen!" Wie findest Du den Fehler, woran es liegt - Angenommen es ist ein Bug in der Anwendung?
Indem ich das Programm debugge und den Fehler nachstelle. Und wie gesagt, ich benutze Exceptions nicht zum Debuggen.
Wen interessierts obs am String oder am Stream gelegen hat?
Stimmt, das interessiert nicht. Aber der grundsätzliche Fehlertyp und der grobe Fehlerort interessiert schon.
Der grundsätzliche Fehlertyp ist der Typ der Exception (std::bad_alloc in dem Fall), der grobe Fehlerort ist der Ort wo ich die Exception behandle (im Fall von bad_alloc ists ein programmweiter oder gar systemweiter Fehler).
Bei Containerzugriffen gehe ich nicht davon aus dass ich Exceptions bekomme, sonst wäre die Programmlogik falsch an der Stelle. bad_alloc rauscht durch, weil da nichts mehr zu retten ist. Andere Exceptions fallen mir im Augenblick nicht ein.
Das heißt Container-Fehler wie out_of_range behandelst Du gar nicht und lässt das Programm sich nur beenden?
Wenn ein out_of_range auftritt, dann tritt er auf weil ich einen Index nicht vorher auf Gültigkeit überprüft habe, wo es nötig gewesen wäre. Und dann tritt er nicht erst auf wenn die Software beim Kunden ist sondern in den Unit-Tests unmittelbar nachdem ich ihn eingebaut habe. Und dann weiß ich sehr genau wo er auftritt und wie ich ihn eingebaut hab. Wenn er erst beim Kunden auftritt dann heißt das dass meine Testabdeckung Mist ist und dann hab ich ein noch viel größeres Problem als nur festzustellen in welcher Funktion genau der Fehler aufgetreten ist.
Was ist mit (internen/temporären) Streams? Fragst Du die Fehler-Bits ab oder nutzt Du die exceptions?
Ich frag die Fehler-Bits ab, wenn es im normalen Programmverlauf zu ungültigen Streams kommen kann (z.B. bei fehlerhaften Usereingaben).
Exceptions sind wirklich Ausnahme-Fehler. Sie treten nicht am laufenden Band auf, wenn das Programm korrekt geschrieben wurde.
Wenn wie bei dir zu Debug-Zwecken die Exceptions mitgeloggt werden sollen, kann man sich überlegen im Debugmodus eine entsprechende Behandlung einuzbauen, und nur dort. Im Releasemodus wo das Programm zu 99% der Fälle ordentlich laufen sollte sind ständige try/catch Blöcke eine Performanceeinbuße, die nicht enthalten sein sollte. Schließlich ist das Programm fertig debugged und ausgeliefert, also brauchen die Exceptions nicht mehr mitgeloggt und nur selten übersetzt werden.
Die C-Style if-else-kaskaden-Fehlerlogging-Politik deiner Firma ist ein wenig out of date in C++-Code. Exzessive Fehlercodeabfragen und Funktionalitätstests über den gesamten Code verstreut sind abgelöst durch Exceptions (eben an Stelle der if-else-Kaskaden) und Unit-tests. Wenn du die C-Style "if(fehlercode1) else if (fehlercode2...)" ablöst durch ein "try catch(exception1) catch(exception2)", hast du nichts gewonnen. Exceptionhandling ist eben was anderes als Fehlercodeabfragen.
Genauso wird richtige Funktionsweise nicht mehr durch "if (hatfunktioniert)" im Code getestet sondern durch Unit-Tests, die außerhalb des Codes stehen.
Dadurch wird der Code nochmal deutlich strukturierter und lesbarer, weil er sich aufs Wesentliche (die Programmlogik) konzentriert und nicht zu 60% aus fehlerbehandlung, zu 30% aus Funktionalitätstest und zu 10% aus Programmlogik besteht, alles schön verquirlt mit einem Haufen Conditionals.
-
Ahh... danke, es wird immer heller bei mir!

Man sollte also folgendermaßen vorgehen:
Logik-Fehler/Programmfehler werden durch Debugging(z.B. Unit-Tests) beseitigt und nach dem Test als "nicht möglich" nicht weiter behandelt (z.B. out_of_range).
Exceptions nutzt man für Fehler, die man beim Debuggen in Unit-Test etc. nicht finden kann oder man nicht kontrollieren kann. Also eher Laufzeitfehler wie, Dateizugriffsfehler, fehlender Speicher, missglückte Win-API-Aufrufe z.B..
Exceptions werden dort gefangen, wo man entsprechend Handeln kann (nochmal Probieren, Alternative einschlagen oder sinnvollen Fehler generieren (wie "Datei konnte nicht geöffnet werden!" -Warum genau ist nicht relevant) ).
-
Roger Wilco schrieb:
Exceptions werden dort gefangen, wo man entsprechend Handeln kann (nochmal Probieren, Alternative einschlagen oder sinnvollen Fehler generieren (wie "Datei konnte nicht geöffnet werden!" -Warum genau ist nicht relevant) ).
Warum genau steht mit etwas Glück sogar in der Exception (std::exception::what()) - allerdings nicht immer, manche Implementierungen sind da mit Infos eher knauserig.
Sonst denke ich ists hell genug geworden

-
Dann eine Frage zum Schnittstellen-Design bezüglich Exceptions:
So wie ich das Verstanden habe, lasst Ihr die Methoden Eurer Klassen prinzipiell sowohl std::exceptions (oder lässt Sie durch) als auch eigene Exceptions werfen, richtig?
-
Roger Wilco schrieb:
So wie ich das Verstanden habe, lasst Ihr die Methoden Eurer Klassen prinzipiell sowohl std::exceptions (oder lässt Sie durch) als auch eigene Exceptions werfen, richtig?
Es fliegen quasi nie std::exceptions. Mir fällt jetzt ausser at() keine methode ein wo eine exception fliegt.
aber natürlich kann es sinn machen solche exceptions in higher level exceptions umzuwandeln.
was das loggen betrifft: du kannst uU uncaught_exception() in einem dtor verwenden um exceptions zu loggen. stackguards können für sowas ganz gut verwendet werden.
oder natürlich du kannst __FILE__ und __LINE__ ja an die exception übergeben. ganz selten sieht man auch unwind-code ala:
#define STACKGUARD_START try { #define STACKGUARD_END } catch(tracable_exception& e) { e.add_trace(__FILE__, __LINE__); throw; } void foo() { STACKGUARD_START; throw bar(); STACKGUARD_END; }
-
Shade Of Mine schrieb:
Es fliegen quasi nie std::exceptions. Mir fällt jetzt ausser at() keine methode ein wo eine exception fliegt.
Stimmt eigentlich, wenn man die std::bad_alloc weg lässt, weil man da eh nichts tun kann sind es eigentlich nur noch std::out_of_range und das theoretische std::lenght_error.
Shade Of Mine schrieb:
aber natürlich kann es sinn machen solche exceptions in higher level exceptions umzuwandeln.
Ja, wie das Beispiel Dateioperation. Aber grundsätzlich fängt man nicht alle std::esceptions und wirft was eigenes, nur um eine einheitliche oder std-freie Schnittstelle bezüglich Exceptions zu besitzen, oder? Damit gehören dann z.B. std::bad_alloc oder std::lenght_error mit zur Schnittstelle der eigenen Klassen.
-
Roger Wilco schrieb:
Aber grundsätzlich fängt man nicht alle std::esceptions und wirft was eigenes, nur um eine einheitliche oder std-freie Schnittstelle bezüglich Exceptions zu besitzen, oder?
Grundsätzlich nicht, nein. Wenn es sinn macht kann man das tun, aber nicht um eine std::xyz_exception in eine mynamespace::XYZException umzuwandeln sondern eher um z.B. bei der wüsten Kalkulation mit zig 3D-Vectoren irgendeines frameworks, std::containerzugriffen und diversen anderem Kram alles einzusammeln und eine allgemeine CouldNotCalculateMyGrandmasAnnuityException zu werfen. Ich machs im Grunde sogar so, dass ich meine Exceptions von std::exceptions ableite, ein sinnvolles what() beisteure udn so dafür sorge dass im Zweifel an den Grenzen meines Zuständigkeitsbereichs alles mit einem "catch(std::exception& e)" gefangen werden kann.
Damit gehören dann z.B. std::bad_alloc oder std::lenght_error mit zur Schnittstelle der eigenen Klassen.
Jein. Sie können zwar aus der benutzung deiner Klassen heraus fliegen, gehören aber nicht eigentlich zur Schnittstelle. Das Excepion-System ist ein recht eigenes System im Vergleich zu den "normalen" Klassenhierarchien.
-
Shade Of Mine schrieb:
was das loggen betrifft: du kannst uU uncaught_exception() in einem dtor verwenden um exceptions zu loggen. stackguards können für sowas ganz gut verwendet werden.
Da man im Prinzip beliebig grosse Programmteile während des Stack-Unwindings laufen lassen kann, ist std::uncaught_exception() eigentlich total witzlos.
Stell dir vor du lässt in einem dtor irgendwelchen Cleanup-Code laufen.
Der Cleanup-Code läuft dann mit std::uncaught_exception() == true, obwohl im Cleanup-Code selbst garkeine Exceptions geworfen werden. Der Cleanup-Code würde dann die ganze Zeit irgendwelchen Schwachsinn loggen, weil er glaubt dass ne Exception von etwas geworfen wurde was er selbst machen wollte. Was aber garnicht stimmt.Faustregel: std::uncaught_exception() kann man vergessen, weil es immer Fälle gibt, in denen es nicht das tut zurückliefert was man sich erwarten würde.
-
pumuckl schrieb:
...um z.B. bei der wüsten Kalkulation mit zig 3D-Vectoren irgendeines frameworks, std::containerzugriffen und diversen anderem Kram alles einzusammeln und eine allgemeine CouldNotCalculateMyGrandmasAnnuityException zu werfen. Ich machs im Grunde sogar so, dass ich meine Exceptions von std::exceptions ableite, ein sinnvolles what() beisteure udn so dafür sorge dass im Zweifel an den Grenzen meines Zuständigkeitsbereichs alles mit einem "catch(std::exception& e)" gefangen werden kann.
Das war nun auch meine Vorstellung. Nur frage ich mich, wann soll ich das machen. Das "Einsammeln" und werfen einer zusammenfassenden Exception. Man könnte es ja theoretisch bei jeder Methode (die fehlschlagen könnte) machen. Das wäre schön einheitlich, erhöht aber die Anzahl Exception-Klassen. Oder man wägt ab, bei welcher Menge an "Low-Level"-Exceptions man das macht.
Jein. Sie können zwar aus der benutzung deiner Klassen heraus fliegen, gehören aber nicht eigentlich zur Schnittstelle. Das Excepion-System ist ein recht eigenes System im Vergleich zu den "normalen" Klassenhierarchien.
Aber ein Kunde Deiner Klasse weiß ja im Grunde nichts über Deine Implementierung. Dann muss er auch wissen, was da so alles geflogen kommen kann. Das gehört für mich dann schon zur Schnittstelle.
-
Roger Wilco schrieb:
Aber ein Kunde Deiner Klasse weiß ja im Grunde nichts über Deine Implementierung. Dann muss er auch wissen, was da so alles geflogen kommen kann. Das gehört für mich dann schon zur Schnittstelle.
Deshalb ja auch "jein". Grundsätzlich kann ein Client damit rechnen, dass ein std::bad_alloc aus so ziemlich jeder Funktion geflogen kommen kann die kein Destruktor ist und nicht swap heißt (die beiden sollten NIE Exceptions werfen bzw. zulassen). Andere Fehler die wirklich nicht behandelbar sind (z.B. Framework xy hat sich komplett verabschiedet und ist nicht mehr zu retten, das Programm liegt in Trümmern...) können auch undokumentiert durchrauschen. Alles weitere kommt entweder aus meinem Bereich oder kam aus einem tiefer liegenden Bereich und wird in dem Fall von mir an den Grenzen übersetzt.