Exception Frage
-
hustbaer schrieb:
@Shade Of Mine:
"might-throw" oder "no-throw" ist oft eine recht wichtige Info, wenn man zuverlässige Software schreiben will.Zu sagen dass Exception-Specs total sinnlos sind, halte ich daher für übertrieben.
Ja, aber es wäre möglich, daß
void swap(...) throw();schlechter ist als
void swap(...)// throw();
-
hustbaer schrieb:
@Shade Of Mine:
"might-throw" oder "no-throw" ist oft eine recht wichtige Info, wenn man zuverlässige Software schreiben will.Zu sagen dass Exception-Specs total sinnlos sind, halte ich daher für übertrieben.
void swap(...) throw();garantiert noch lange kein "no-throw" und darum ist es sinnlos.
Außerdem finde ich die Syntax total dämlich.
-
hustbaer schrieb:
Zu sagen dass Exception-Specs total sinnlos sind, halte ich daher für übertrieben.
Zeig mir eine Situation wo Exception Spezifikationen mehr Sinn machen als sie wegzulassen.
Bedenke: wenn du
void foo() throw() { mightThrow(); }schreibst, generiert der compiler daraus ein:
void foo() throw() { try { mightThrow(); } catch(...) { std::unexpected(); } }das alleine ist ja schon etwas dass man nie haben will. terminate() ist boese. die einzige halbwegs sinnvolle argumentation ist dann, dass man ja set_unexpected machen kann und nen eigenen unexpected handler benutzen kann. nur hat das das problem dass du keinen kontext zu dem fehler hast. dh, dein unexpected handler kann im prinzip nur loggen was passiert ist.
man muss also schon etwas sehr krankes konstruieren damit der unexpected handler sinn machen kann.
oder man versucht was lustiges mit bad_exception zu bauen - aber das ist dann auch wieder doof, weil logik fehler (und falsche exception geworfen IST ein logikfehler) ja eigentlich nicht so versteckt werden sollten...
und wir sehen mal davon ab dass exception spezifikationen etwas sind, dass durchaus von der grundidee her schon nicht ganz diskussionfrei ist. siehe zB http://www.artima.com/intv/handcuffs.html
dazu noch die schlechte implementierung in c++... daraus folgt: finger weg davon (in c++).
-
@Shade:
das alleine ist ja schon etwas dass man nie haben will. terminate() ist boese. die einzige halbwegs sinnvolle argumentation ist dann, dass man ja set_unexpected machen kann und nen eigenen unexpected handler benutzen kann. nur hat das das problem dass du keinen kontext zu dem fehler hast. dh, dein unexpected handler kann im prinzip nur loggen was passiert ist.
wieso sollte terminate() böse sein? terminate() ist toll!
und ja, genau, es geht um set_unexpected(), da schreibt man nen crash-dump und "crasht" dann eben (kontrolliert).oder man versucht was lustiges mit bad_exception zu bauen - aber das ist dann auch wieder doof, weil logik fehler (und falsche exception geworfen IST ein logikfehler) ja eigentlich nicht so versteckt werden sollten...
ja, genau, ein logikfehler der nicht versteckt werden sollte. also ist terminate() doch geradezu ideal, nicht?
und wir sehen mal davon ab dass exception spezifikationen etwas sind, dass durchaus von der grundidee her schon nicht ganz diskussionfrei ist.
ich bin nicht für checked-exceptions, ich bin ganz stark dagegen. ich finde es aber gut optional eine no-throw garantie geben zu können, die dann auch von der toolchain forciert wird.
dadurch kann ich funktionen explizit als "safe" markieren, und der programmierer weiss dann, dass er sie z.B.. in xyz::swap problemlos aufrufen kann. oder in einen destruktor oder wo auch immer es wirklich nötig ist, selbst eine no-throw garantie geben zu können.
IMO ist der support den eine konforme C++ implementierung über unexpected() gibt hier immer noch besser, als garkein support von der toolchain.noch besser wäre natürlich eine toolchain die warnings produziert, wenn man etwas als no-throw deklariert, was aber funktionen aufruft die bekanntermassen exceptions werfen können. aber gut, das ist im moment bloss träumerei, da es AFAIK kein C++ compiler kann (bzw. tut).
----
nur weil etwas, wie die exceptions-specs in C++, ziemlich daneben geraten ist, heisst das ja nicht, dass man es komplett ignorieren müsste bzw. für überhaupt garnichts verwenden kann.
-
hustbaer schrieb:
wieso sollte terminate() böse sein? terminate() ist toll!
und ja, genau, es geht um set_unexpected(), da schreibt man nen crash-dump und "crasht" dann eben (kontrolliert).
[..]
ja, genau, ein logikfehler der nicht versteckt werden sollte. also ist terminate() doch geradezu ideal, nicht?Du hast keinen Context. Deshalb ist der unexpected handler ja so sinnlos.
dadurch kann ich funktionen explizit als "safe" markieren, und der programmierer weiss dann, dass er sie z.B.. in xyz::swap problemlos aufrufen kann. oder in einen destruktor oder wo auch immer es wirklich nötig ist, selbst eine no-throw garantie geben zu können.
IMO ist der support den eine konforme C++ implementierung über unexpected() gibt hier immer noch besser, als garkein support von der toolchain.Naja, du hast dadurch schon einen relevanten performance penalty. wenn wir davon ausgehen dass du nur throw() verwendest (denn andere spezifikationen machen wegen templates eh keinen sinn und exception neutrale funktionen sind sowieso das beste) hast du also die ganzen schoenen kurzen funktionen ploetzlich in der unwind-list drinnen stehen. was im falle eines std::swap vermutlich gleich den generierten code fuer diese funktion mehr als verdreifacht.
und was genau ist der vorteil davon? als "safe" markieren kann ich sie ja auch ohne exception spezifikationen... unexpected hilft dir ja nicht - da waere es schon sinnvoller guards in die no-throw funktion zu stecken. im dtor checkt man dann ob uncaught_exception true ist und kann auch viel sauberer logen.
weiters kann man das dann auch super per #define an und abschalten (waehrend du bei exception spezifikationen ja ploetzlich interfaces aendern wuerdest).
noch besser wäre natürlich eine toolchain die warnings produziert, wenn man etwas als no-throw deklariert, was aber funktionen aufruft die bekanntermassen exceptions werfen können. aber gut, das ist im moment bloss träumerei, da es AFAIK kein C++ compiler kann (bzw. tut).
es ist ja auch nicht moeglich dass ein compiler das unterstuetzt. da ja zB fast die komplette standard library keine exception spezifikationen verwendet (bis auf das dumme throw() bei exception::what).
nur weil etwas, wie die exceptions-specs in C++, ziemlich daneben geraten ist, heisst das ja nicht, dass man es komplett ignorieren müsste bzw. für überhaupt garnichts verwenden kann.
was heisst es dann?
es ist ein fragwuerdiges feature das schlecht implementiert ist und wenig compiler support hat und keinen mehr nutzen bringt.
wenn das kein grund ist es nicht zu verwenden, dann weiss ich auch nicht.
-
Shade Of Mine schrieb:
terminate() ist boese.
Wenn ein Programm so einen fundamentalen Fehler enthält (in der Fehlerbehandlung), dann kann man effektiv nichts anderes mehr machen als sofort und instantan das Programm abzubrechen. Jede Verschleppung des Fehlers verschlimmert nur noch die Fehlersuche, und ist daher noch schlechter.
Wenn die Exceptionsspecprüfung bisher nur zur Laufzeit erfolgte, so ist das doch kein Grund diese nun komplett heraus zu nehmen. Es wäre sehr viel besser gewesen, Funktionen mit throw() beim Compilieren zu überprüfen und je nach Bedarf dann den notwendigen Code einzubauen. Elementare Funktionen und C-Funktionen sind grundsätzlich throw(), und es wäre so ein leichtes Funktionen entsprechend zu optimieren.
Im Zusammenhang mit RAII ergibt es keinerlei Sinn Destruktoren ohne throw() zu deklarieren! Denn wenn man Destruktoren etwas werfen läßt, landet man früher oder später bei unexpected(). Da kann man das Verhalten auch gleich erzwingen.
So bleibt im Grunde als einziger Grund throw() nicht zu verwenden, schlechte Compiler die nicht optimieren können.
-
Ausgraber

~john schrieb:
So bleibt im Grunde als einziger Grund throw() nicht zu verwenden, schlechte Compiler die nicht optimieren können.
Neben der Verwaschung relevanter Informationen. Ich fände es viel wichtiger, exception-werfende Funktionen speziell kennzuzeichnen, als nicht werfende. Denn diese sind nämlich die Ausnahme (schon per Definition) und vor allem der Teil, der besondere Aufmerksamkeit vom Benutzer erfordert. Es gibt zum Beispiel Frameworks, die als bewusste Designentscheidung auf Exceptions ganz verzichten (und damit meine ich nicht antike wie wxWidgets, welche vor modernem C++ entstanden sind). Welchen Sinn hätte es, wenn dort jede Funktion ein
throw()besässe?Man muss vielleicht auch zwei Kontexte unterscheiden: Zum einen Funktionen auf hoher Abstraktionsebene, z.B. von Framework-Schnittstellen, zum anderen elementare Low-Level-Operationen wie Non-Throwing Swap. Bei ersteren kann oft nicht garantiert werden, dass keine Exception fliegt. Es könnte ja irgendeine Kopie fehlschlagen, weil der Speicher ausgeht. Halt Fälle, die nie auftreten, aber alles lahm legen, falls sie doch auftreten. Auf dieser Ebene macht es meiner Meinung nach Sinn, "beabsichtigte" Exceptions zu dokumentieren, ansonsten nichts.
Beim Low-Level-Fall finde ich es hingegen angebracht,
throw()– oder vielleicht besser einen Dokumentationshinweis – hinzuschreiben, falls wirklich unter keinen Umständen eine Exception fliegen kann. Schliesslich muss man hier möglichst generisch bleiben und dafür sorgen, dass grundlegende Dinge wie Speicherfreigabe noch funktionieren. Also Gegebenheiten, auf die sich der Benutzer 100% verlassen kann.
-
Nexus schrieb:
Ich fände es viel wichtiger, exception-werfende Funktionen speziell kennzuzeichnen, als nicht werfende. Denn diese sind nämlich die Ausnahme (schon per Definition) und vor allem der Teil, der besondere Aufmerksamkeit vom Benutzer erfordert.
Das ist bei mir aber anders. Fast jede Funktion benutzt Freispeicher oder was grafisches oder was mit Dateien oder irgendwelche anderen Ressourcen, die kurz mal nicht da sein könnten. Entweder direkt oder indem sie Funktionen aufruft, die das Problem haben.
Nexus schrieb:
Es gibt zum Beispiel Frameworks, die als bewusste Designentscheidung auf Exceptions ganz verzichten (und damit meine ich nicht antike wie wxWidgets, welche vor modernem C++ entstanden sind).
Diese Frameworks sind ein einziger Designfehler? Weiß nicht, aber es fühlt sich unglaublich komisch an. Werden alle Probleme wie damals per return hochgereicht?
Nexus schrieb:
Man muss vielleicht auch zwei Kontexte unterscheiden: Zum einen Funktionen auf hoher Abstraktionsebene, z.B. von Framework-Schnittstellen, zum anderen elementare Low-Level-Operationen wie Non-Throwing Swap. Bei ersteren kann oft nicht garantiert werden, dass keine Exception fliegt. Es könnte ja irgendeine Kopie fehlschlagen, weil der Speicher ausgeht. Halt Fälle, die nie auftreten, aber alles lahm legen, falls sie doch auftreten. Auf dieser Ebene macht es meiner Meinung nach Sinn, "beabsichtigte" Exceptions zu dokumentieren, ansonsten nichts.
Uih, da bin ich nicht so sicher. Ja, es mag so erscheinen, als müsse man sich am besten fesseln, damit man keine Fehler mehr macht. Aber ist das nicht neulich mit Java doll in die Hose gegangen? Das Framework steht zwischen den OS-Wrappern und der Anwendungslogik und die Anwendungslogik muß auf Exceptions des OS reagieren.
Nexus schrieb:
Beim Low-Level-Fall finde ich es hingegen angebracht,
throw()– oder vielleicht besser einen Dokumentationshinweis – hinzuschreiben, falls wirklich unter keinen Umständen eine Exception fliegen kann. Schliesslich muss man hier möglichst generisch bleiben und dafür sorgen, dass grundlegende Dinge wie Speicherfreigabe noch funktionieren. Also Gegebenheiten, auf die sich der Benutzer 100% verlassen kann.Jup.
Viele der elementaren Funktionen sind Templates und können deswegen nichts sagen. Warum eigentlich nicht? Wenn schon Exceptionspezifikationen, mußte das eine Compilersache sein und keinen Laufzeitfehler erzeugen. Dazu müßten sie per Template-Meta-Magie vom Benutzer erzeugt werden können. Und selbst dann wäre es nichts, was ich mögen würde, wegen zu viel Aufwand ohne erkennbaren Sinn, dem Anzeichen einer Softwareseifenblase (Ich erinnere an die Ungarische Notation, Struktogramme, Strukturierte Programmierung und UML).
Ok, ein nothrow verstehe ich sehr gut, insbesondere wird es lecker, wenn man das zur Compilezeit abfragen kann und anhand dessen seinen Code anpassen kann. Ich spüre des öfteren das Verlangen, etwas Einfaches einfach zu lassen und nicht (wegen der Annahme, daß eine Zuweisung evtl nicht klappen könnte) exceptionsicher zu machen.
//soll entweder von begin bis end alles füllen oder gar nichts void fill(T const& value,T* begin,T* end) { queue<T> tmp; for(T* pos=begin;pos!=end;++pos) tmp.push_back(value); #ab_jetzt_nothrow for(T* pos=begin;pos!=end;++pos) { swap(*pos,tmp.peek_front(); tmp.pop_front(); } }//soll entweder von begin bis end alles füllen oder gar nichts //wenn ich doch nur wüßte, daß der op=(T,T) nothrow ist... *träum* void fill(T const& value,T* begin,T* end) { while(begin!=end) *begin=value; }
-
volkard schrieb:
Das ist bei mir aber anders. Fast jede Funktion benutzt Freispeicher oder was grafisches oder was mit Dateien oder irgendwelche anderen Ressourcen, die kurz mal nicht da sein könnten. Entweder direkt oder indem sie Funktionen aufruft, die das Problem haben.
Ja, Freispeicher benutze ich auch oft. Meistens zwar nicht direkt, aber in Klassen, die sich Speicher holen. Aber eben: Auf hoher Abstraktionsebene kann dauernd etwas schiefgehen. Ich bin mir nicht sicher, ob sich ~john auch für
throw()ausspricht, wenn keine beabsichtigten Exceptions fliegen können. Ich denke nicht (d.h.throw()nur wenn wirklich rein gar nichts fliegt), dann stehtthrow()wieder nur an sehr wenigen Orten und ich bin wieder gleicher Meinung. Sorry, wenn ich da was durcheinandergebracht habe.Was ich damit sagen will:
/// @throw FileNotFound Datei konnte nicht gefunden werden void LoadData(const char* fileName);ist für mich viel wertvoller als
/// @throw std::bad_alloc Kein Speicher für Eintrag void AddEntry(const Entry& entry)Denn wie will man auf das zweite sinnvoll reagieren? Jeder Versuch könnte seinerseits wieder Probleme verursachen. Abgesehen davon müsste man einen sehr grossen Teil der Funktionen mit so einer Information zupflastern.
volkard schrieb:
Diese Frameworks sind ein einziger Designfehler? Weiß nicht, aber es fühlt sich unglaublich komisch an. Werden alle Probleme wie damals per return hochgereicht?
Designfehler würde ich nicht sagen. Zum Beispiel scheint mir das Multimedia-Framework SFML sehr gut durchdacht und in modernem C++ geschrieben zu sein, aber es verwendet keine Exceptions. Fehlerquellen gibts hauptsächlich beim Laden von Ressourcen (Bilder, Schriftarten etc.). Da hat der Entwickler sich entschieden,
bool-Rückgaben zu verwenden. Man kann sich darüber streiten, ob es besser ist. Soweit ich weiss, war einer der Gründe, dass man mit dem Programm dennoch weiterarbeiten kann. Also dass statt eines Bilds ein weisses Rechteck angezeigt wird. Ich kann mir durchaus vorstellen, dass so eine Semantik erwünscht sein kann.An den Stellen, wo ich auf korrekt geladene Ressourcen angewiesen bin, habe ich mir Wrapper geschrieben, die Exceptions werfen können. So muss ich trotzdem nicht C-Style-Hochreichen betreiben.
volkard schrieb:
Uih, da bin ich nicht so sicher. Ja, es mag so erscheinen, als müsse man sich am besten fesseln, damit man keine Fehler mehr macht. Aber ist das nicht neulich mit Java doll in die Hose gegangen? Das Framework steht zwischen den OS-Wrappern und der Anwendungslogik und die Anwendungslogik muß auf Exceptions reagieren.
Ich bin mir nicht sicher, was du meinst. Ich spreche mich jedenfalls nicht für Checked Exceptions wie in Java aus.

Um nochmals meine Ansicht zusammenzufassen:
- Exception-Dokumentation bei Highlevel-Exceptions wie
FileNotFound. - Keine Angabe bei Funktionen, die nicht zum Werfen von Exceptions beabsichtigt sind (im Beispiel
AddEntry()). throw(),noexceptoder Dokumentation bei generischen Low-Level-Funktionen, die für Exceptionsicherheit essentiell sind.
- Exception-Dokumentation bei Highlevel-Exceptions wie
-
volkard schrieb:
//soll entweder von begin bis end alles füllen oder gar nichts void fill(T const& value,T* begin,T* end) { queue<T> tmp; for(T* pos=begin;pos!=end;++pos) tmp.push_back(value); #ab_jetzt_nothrow for(T* pos=begin;pos!=end;++pos) { swap(*pos,tmp.peek_front(); tmp.pop_front(); } }1. Python Kommentare funktionieren nicht in C++.
2. Das normal swap ist nicht non-throw http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-throwing_swap