Fehlerhandling im Konstruktor
-
Hallo,
ist es möglich, im Konstruktor auf einen aufgetretenen Fehler zu reagieren, und dies der instanzierenden Klasse mitzuteilen?
Über Flags und Errorcallbacks möchte ich dies nicht realisieren.Kann man "MyClass" auf "NULL" setzen, so dass ich dies in der übergeordneten Klasse dann prüfen kann?
MyClass::MyClass(char *pcFilename) { File *pFile; if((pFile = fopen(pcFilename, "r")) == NULL) { //geht so natürlich nicht this = NULL; return; } }mfg
-
Du kannst im Konstruktor eine Exception werfen, was dazu führt dass das Objekt garnicht erst erzeugt wird.
-
Eleganterweise mit exceptions
tinyoon schrieb:
MyClass::MyClass(char *pcFilename) throw(YourErrorException) { File *pFile; if((pFile = fopen(pcFilename, "r")) == NULL) { //geht so natürlich nicht throw YourErrorException(); } }mfg
-
Klasse.
Danke für die schnelle Hilfe, so funktioniert es sauber:)mfg
-
PhilippHToner schrieb:
Eleganterweise mit exceptions
Das würde ich aber nicht übertreiben. Ich würde das dann tun wenn ich unbedingt eine Invariante unbedingt brauche oder es wirklich einen schwerwiegenden Fehler betrifft, für den sich auch der Benutzer der Klasse interessiert, d.h. den er auch behandeln sollte. So wie das bei dir aussieht würde ich mich z.B. lieber am interface der iostreams orientieren und keine exception werfen (stattdessen das Objekt auf einen NULL-Zusand setzen). Exceptions sind keine Rückgabewerte.
-
GorbGorb schrieb:
PhilippHToner schrieb:
Eleganterweise mit exceptions
Das würde ich aber nicht übertreiben. Ich würde das dann tun wenn ich unbedingt eine Invariante unbedingt brauche oder es wirklich einen schwerwiegenden Fehler betrifft, für den sich auch der Benutzer der Klasse interessiert, d.h. den er auch behandeln sollte. So wie das bei dir aussieht würde ich mich z.B. lieber am interface der iostreams orientieren und keine exception werfen (stattdessen das Objekt auf einen NULL-Zusand setzen). Exceptions sind keine Rückgabewerte.
Naja wir coden hier C++ also wieso keine exceptions? Klar kann man auch den bool operator!() implementieren, der beim Fehler true wird wie es die stream anbieten. Es kommt halt drauf an, inwiefern es fatal ist, wenn der stream zur Datei nicht geöffnet wird. Wenn ich ein Programm bau, z.B. eine Datei verschlüsselt ist sowas auf jeden Fall eine exception Wert, weil das Programm ansonsten keine Aufgaben mehr erledigen kann. Wenn das Programm beispielsweise einfach Dateien durchsucht und manche Dateien erlauben das Lesen oder SChreiben nicht, dann könnte man mit sanften Zuständen arbeiten ja

-
GorbGorb schrieb:
So wie das bei dir aussieht würde ich mich z.B. lieber am interface der iostreams orientieren und keine exception werfen (stattdessen das Objekt auf einen NULL-Zusand setzen). Exceptions sind keine Rückgabewerte.
Ja, und ich behaupte das Gegenteil.
Objekte mit Zombie-Zustand sind Mist.
Klassen mit "vielleicht" Konstruktor sind ebenso Mist.
Wenn man umbedingt Zombie-Files anbieten will, kann man immer noch einen Default-Ctor + TryOpen() Funktion machen.
-
GorbGorb schrieb:
So wie das bei dir aussieht würde ich mich z.B. lieber am interface der iostreams orientieren und keine exception werfen (stattdessen das Objekt auf einen NULL-Zusand setzen).
Jein. Wenn das Feature der Klasse, dessen Erzeugung Probleme macht, ein optionales Feature ist, dann braucht und sollte man keine Exceptions werfen. Wenn andersrum das Feature ein must-have ist und die Existenz eines Objekts ohne dieses Feature keinen Sinn macht, dann sollte man Exceptions werfen und sich nicht mit Zombies abgeben.
std::fstreams sind da in einer Grazone - Ein Stream, der nicht streamen kann, weil er keiner Datei zugeordnet ist, macht eigentlich keinen Sinn. Andererseits möchte man vielleicht die Ausgabe "verteilen", d.h. den stream zwischendurch auf eine andere Datei umleiten, oder man möchte mangels anderer Möglichkeiten vielleicht erstmal schauen, ob die Zieldatei existiert/beschreibbar ist etc. Dazu ist ein öffnen/schließen von Dateien sinnvoll und es muss der besagte Kompromiss mit isopen() und ohne Exceptions eingeganen werden.
Das ist aber ein Spezialfall und sollte nicht als allgemeines Designpattern für Konstruktorfehler kopiert werden.
-
hustbaer schrieb:
Wenn man umbedingt Zombie-Files anbieten will, kann man immer noch einen Default-Ctor + TryOpen() Funktion machen.
Das fände ich auch schöner.
Das Problem an exceptions ist doch, dass ihre performance Charakteristika für einen sehr seltenen Gebrauch sprechen (eben bei außergewöhnlichen Situationen). Wenn man aber eine Klasse oder eine Funktion schreibt, die immer wieder in unterschiedlichen Kontexten verwendet wird, kann man nicht genau bewerten, ob ein Fehler jetzt unerwartet aufkommt oder vielleicht nur auf ein Auftreten dieses Fehlers geprüft wurde. Ein Beispiel von boost, das ich so nicht schön finde:int main(int argc, char * argv[]) { using boost::lexical_cast; using boost::bad_lexical_cast; std::vector<short> args; while(*++argv) { try { args.push_back(lexical_cast<short>(*argv)); } catch(bad_lexical_cast &) { args.push_back(0); } } ... }http://www.boost.org/doc/libs/1_48_0/doc/html/boost_lexical_cast/examples.html
Fühlt sich das nur für mich falsch an? Es wäre meines Erachtens nach einfach sinnvoller ein Objekt im "Zombiezustand" zurückzugeben (z.B. boost::optional) und den user entscheiden zu lassen, ob jetzt was Ernsthaftes schief gegangen ist oder nicht.
Was ich im Grunde sagen will: Was eine exception Wert ist kontextabhängig, man sollte es daher nicht übertreiben, vor allem bei häufig wiederverwendetem code. Normale Logik in einen catch Block zu schreiben (wie in dem Beispiel oben) ist schlechter Stil.
-
Also ich habe mal nachgelesen, dass exception handling NICHT langsamer ist. Es gibt einen Art exception handling (LIFO) Stack, der beim Eintreten in einen try block einfach pushed wird mit den catch Ausdrücken und beim Austreten all diese wieder gepopt werden. Wenn nun eine exception geworfen wird muss das Prog einfach zur Laufzeit den Stack von oben nach unten durchsuchen und an die Adresse jumpen.
Klar, wenn der Code im 135. Try Block ist, und ich schmeiss ne exception, die ganz früh catched wurde, müssen halt viele compares gemacht werden.
Kann sein, dass der Compiler es auch noch optimieren kann, weiß ich nicht. Aber theoretisch müsste er mit den generierten Klassen-Ids arbeiten, weil es zur Laufzeit keine Klassen gibt und exception handling ja auch mit vererbten Klassen funktioniert. Also sowas wie die Funktionstabelle virtueller Funktionen.
Unabhängig, wie performant sie arbeiten, ist es einfach so, dass viele ein Problem mit der alten Programmierung haben, wo man mit goto und Marken gearbeitet hat und deswegen Exception und switch Blöcke nicht erwünscht sind.
Nach dem Motto: das Programm arbeitet Blöcke von oben nach unten ab.
-
PhilippHToner schrieb:
Also ich habe mal nachgelesen, dass exception handling NICHT langsamer ist. Es gibt einen Art exception handling (LIFO) Stack, der beim Eintreten in einen try block einfach pushed wird mit den catch Ausdrücken und beim Austreten all diese wieder gepopt werden. Wenn nun eine exception geworfen wird muss das Prog einfach zur Laufzeit den Stack von oben nach unten durchsuchen und an die Adresse jumpen.
Soviel ich weiß müssten exceptions nichts kosten, solange sie nicht geworfen werden. Wenn sie aber dann doch fliegen ist der performance overhead deutlich höher als bei einem simplen return.
Exceptions sind aber nur ein Aspekt, warum man es nicht übertreiben sollte. Der andere ist, dass man Logik in catch Blöcke schreiben muss (siehe boost Beispiel).