Schnitstellen in C++?
-
Yamakuzure schrieb:
Nein. Je mehr ich darüber lese, desto erschütterter bin ich darüber, dass noexcept a) den Programmierer auf "throw()" und nicht-"throw()" beschränkt, und b) keinerlei Möglichkeiten irgendeiner Reaktion bietet, wie "throw()" es tut. Außerdem gibt dir "throw()" ebenfalls die Garantie ab, die du an noexcept lobst.
Nein. Wie soll ich als Compilerbauer auf die Soft-"Garantie" reagieren und auf Benutzer, die im terminate() ihre Datenbank erst wegschreiben?
-
Shade Of Mine schrieb:
Es sind 2 komplett unterschiedliche Sachen. throw() bringt dir defakto nämlich garnichts. So oder so fliegt eine Exception die du nicht erwartest. Nur dass dir bei bei einem throw() im Gegensatz zu keiner Spezifikation die Exception Daten verloren gehen.
Wenn man eine Funktion mit throw() spezifiziert, dann erwartet man daß diese Funktion keinerlei Exceptions wirft. Wenn ich statt dessen einfach nur throw() weglasse erreiche ich, daß das Programm in einem undefinierten Zustand wechselt. Es bringt also rein gar nichts, das Programm noch weiter auszuführen. Viel schlimmer als Datenverlust ist es fehlerhafte Daten in einem System abzulegen, denn diese sind möglicherweise gar nicht mehr so einfach aufzuspüren. Und dann hat man erst recht ein Problem.
Shade Of Mine schrieb:
Sprich: throw() bringt nie etwas. Ich kenne keine Situation wo throw() einen Vorteil gegenüber keiner Spezifikation bringt.
Das Beispiel des Destruktors ist eindeutig pro throw(). Wenn man Exceptions in Destruktoren erlaubt, dann darf auch mehr als eine Exception geworfen werden, was aber C++ grundsätzlich nicht erlaubt. Da solche Fehler möglicherweise gar nicht einfach zu reproduzieren sind, ist es überhaupt nicht sinnvoll auf throw() im Destruktor zu verzichten. Jedes Verschleppen des Programmabbruchs führt dazu, daß man den Fehler nur verschleppt und dessen Behebung um Größenordnungen erschwert.
Shade Of Mine schrieb:
noexecpt dagegen bringt etwas: der Code wird kleiner und schneller.
Erstes Ziel muß es sein, daß ein Programm fehlerfrei ist, danach kommt erst die Ausführungsgeschwindigkeit.
-
krümelkacker schrieb:
Darüberhinaus wird
noexceptein Compile-Zeit-Operator sein, mit dem man Ausdrücke darauf überprüfen kann, ob sie garantiert keine Ausnahme werfen können:bool nx_f = noexcept( f(23) ); // false bool nx_g = noexcept( g(42) ); // false oder true bool nx_h = noexcept( h(17) ); // false oder true bool nx_k = noexcept( k(29) ); // falseMit anderen Worten: Der noexcept-Operator darf pessimistisch sein und immer false zurückgeben.
So ein Scheiß.
-
Das wird toll schrieb:
krümelkacker schrieb:
Darüberhinaus wird
noexceptein Compile-Zeit-Operator sein, mit dem man Ausdrücke darauf überprüfen kann, ob sie garantiert keine Ausnahme werfen können:bool nx_f = noexcept( f(23) ); // false bool nx_g = noexcept( g(42) ); // false oder true bool nx_h = noexcept( h(17) ); // false oder true bool nx_k = noexcept( k(29) ); // falseMit anderen Worten: Der noexcept-Operator darf pessimistisch sein und immer false zurückgeben.
So ein Scheiß.
Das habe ich vorhin nochmal überprüfen wollen und bin auch wieder auf ältere Proposals gestoßen, die ganz anders aussahen:
- N2855: Rvalue References and Exception Safety (März 2009)
- N2983: Allowing Move Constructors To Throw (November 2009)
- N3050: Allowing Move Constructors To Throw (Rev 1) (März 2010)
In n2855 hatte man vorgeschlagen, dass noexcept statisch überprüft werden soll, wobei es einen Fluchtmechanismus gab, den noexcept-Block:
double sqrt(double); noexcept void f(double& x) { if (x>0) { noexcept{ x = sqrt(x); } // OK } sqrt(x); // <-- ill-formed, da sqrt eine Ausnahme werfen könnte }In N2983 ist die Position von noexcept nach hinten gerutscht und nennt sich jetzt "exception specification" statt "noexcept specifier". Die statische Überprüfung wurde nicht mehr gefordert. Man sprach sich für potentiell Ausnahme-werfende Move-Konstruktoren aus und führte konservative xxx_nothrow_xxx Traits-Klassen ein, mit denen überprüft werden können sollte, ob eine bestimmte Operation (xxx) eine Ausnahme werfen kann oder nicht. Und weil das mit den Traits-Klassen überhand nehmen würde, wurde auch noexcept als Compile-Zeit-Operator vorgeschlagen...
It seems that has_nothrow_xxx traits are proliferating (and not just in this proposal). Once we have noexcept(bool-constant-expr) available to make the information available, it makes sense to generalize the traits into an operator similar to sizeof and typeof that can give us answers about any expression. The new operator noexcept(expression) determines whether the given expression can throw.
The noexcept operator is conservative, and will only evaluate true when the compiler can be certain that the expression will not throw,...
...
The result of the noexcept operator is false if in an evaluated context the expression would contain- a potentially evaluated call [Footnote: This includes implicit calls, e.g., the call to an allocation function in a new-expression. -- end footnote] to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification ([except.spec]),
- a potentially evaluated throw-expression ([except.throw]),
- a potentially evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check ([expr.dynamic.cast]), or
- a potentially evaluated typeid expression ([expr.typeid]) applied to an expression whose type is a polymorphic class type ([class.virtual]).
Otherwise, the result is true.
Das klingt allerdings nicht so, als ob der noexcept-Operator die noexcept-Spezifikation missachten darf. Ich denke jetzt, dass mit "konservativ" nur gemeint ist, dass der Compiler neben noexcept keine zusätzlichen Überprüfungen machen soll, also, dass bei
void f() noexcept; void g() { } void h();noexcepf(f()) true zurückgeben muss, noexcepf(g()) false zurückgeben muss (obwohl g offensichtlich keine Ausnahme wirft) und dass noexcept(h()) auch false ist. Mit dieser Interpretation wären zwei von den vier Beispielen von mir am Anfang falsch und der Operator hätte in beiden Fällen true zurückgeben müssen.
Auch die Forderung nach statischer Überprüfung von noexcept wurde entfernt:
In particular, we are not proposing to mandate static checking: a noexcept(true) function can call a noexcept(false) function without causing the program to become ill-formed or generating a diagnostic. Generating a diagnostic in such cases can, of course, be implemented by any compiler as a matter of QOI.
In der letzten Version (N3050) gab es neben einigen Klarstellungen auch die Änderung, dass ...
If an exception escapes a noexcept function, the implementation is required to call std::terminate(). In prior drafts, the behavior was undefined if an exception escapes a noexcept function.
Dass es mal um statische Prüfung von noexcept ging, hatte ich wieder komplett vergessen. Offensichtlich gab es gute Gründe, die dagegen gesprochen haben, sonst hätte man das nicht wieder entfernt. Welche das sind, weiß ich im Moment auch nicht. Im Prinzip überlässt man es ja nur den Compilerherstellern, eine gescheite Warnung auszugeben.
Im Groben und Ganzen finde ich das aber so, wie es zur Zeit aussieht, in Ordnung. Eine Ausnahme, die aus einer noexcept-Funktion fliehen will, ist eben als Programmierfehler zu werten. Im Idealfall kann ein Debugger mir in einer solchen Situation den Aufruf-Stack anzeigen und mir vielleicht noch den Typ der Ausnahme verraten und eventuell noch, woher sie kam. Dann wäre ich schon voll zufrieden.
kk
-
Vielen vielen Dank für deine Recherche, die hat doch einiges Licht ins Dunkel gebracht!
Also stellt es sich jetzt so dar, dass das Verhalten einer
throw()-Spezifikation beibehalten wird, jedoch ohne den Overhead, der durch die Anforderungen derthrow(a,b,c)-Spezifikationen verursacht wird. Ansonsten klingt es für mich so, dass es zusätzlich die Möglichkeit geben soll, Funktionen dahingegen zu prüfen, ob sie garantiert nicht werfen, unabhängig davon, ob sie mitnoexceptspezifiziert sind oder nicht. Bei unspezifizierten Ausdrücken wird aber nurtruegesetzt, wenn der Compiler (also dort in der Compile-Zeit) garantieren kann, dass von dort keine Ausnahme kommen kann.Da das Verhalten bei einem Bruch der
noexcept-Spezifizierung allerdings doch weiter zur Laufzeit stattfindet, bleibt abzuwarten, ob uns Programmierern am Ende Möglichkeiten bleiben, darauf zu reagieren, oder ob kein Weg amterminate()vorbeiführt. (Mir schwant aber, dass es außer dem Setzen eines alternativen terminate-Handlers nichts mehr geben wird.)
-
Yamakuzure schrieb:
Also stellt es sich jetzt so dar, dass das Verhalten einer
throw()-Spezifikation beibehalten wird, jedoch ohne den Overhead, der durch die Anforderungen derthrow(a,b,c)-Spezifikationen verursacht wird.Die Semantik von throw() und throw(a,b,c) wird sich nicht verändern. Dementsprechend sehe ich auch nicht, wie plötzlich ein throw() mit weniger Aufwand implementiert werden können soll als vorher. throw(type-id-listopt) nennt man jetzt nur "dynamic-exception-specification" und unterscheidet es von der "noexcept-specification". Anhang D.5 des aktuellen Drafts enthält die Zeile "The use of dynamic exception specifications is deprecated".
Yamakuzure schrieb:
Ansonsten klingt es für mich so, dass es zusätzlich die Möglichkeit geben soll, Funktionen dahingegend zu prüfen, ob sie garantiert nicht werfen, unabhängig davon, ob sie mit
noexceptspezifiziert sind oder nicht.So interpretiere ich das aber nicht. Ich finde die Formulierungen in den Proposals aber auch etwas komisch. Die Definition des noexcept-Operators sehe ich da als bindend an. Und diese Definition stützt sich klar auf die Ausnahme-Spezifizierungen bei Funktionsaufrufen. Nach meinem derzeitigen Verständnis, sieht es so aus:
void f(); // noexcept(f()) --> false void g() noexcept; // noexcept(g()) --> true void h() noexcept(true); // noexcept(h()) --> true void i() noexcept(false); // noexcept(i()) --> false void j() throw(); // noexcept(j()) --> true int k(int x) {return x+1;} // noexcept(k(2)) --> false void z() throw(int); // noexcept(z()) --> falseObwohl ein schlauer Compiler erkennen könnte, dass beim Aufruf k(3) offensichtlich keine Ausnahme fliegen kann, muss er dennoch false liefern, weil k keine "non-throwing exception specification" hat. In dieser Hinsicht, ist der Operator "konservativ". Die Ausnahmespezifizierungen dürfen aber nicht ignoriert werden, siehe 5.3.7 für die Definition des noexcept-Operators und 15.4/12 für die Definition von "non-throwing exception specification" im aktuellen Entwurf.
Yamakuzure schrieb:
Bei unspezifizierten Ausdrücken wird aber nur
truegesetzt, wenn der Compiler (also dort in der Compile-Zeit) garantieren kann, dass von dort keine Ausnahme kommen kann.Nein. Das Verhalten des noexcept-Operators ist klar definiert. Das muss bei jedem Compiler gleich sein. Folgende Implikation ist falsch: Compiler kann beweisen, dass ein bestimmer Ausdruck x keine Ausnahme werfen kann => noexcept(x). Aber umgekehrt stimmt's. Wenn der Operator true liefert, fliegt garantiert keine Ausnahme. Und wenn dann noch eine Ausnahme aus einer non-throwing Funktion entfliehen will, wird dementsprechend std::unexpected (bei dynamischen Ausnahmespezifizierungen) oder std::terminate (bei noexcept-Spezifizierungen) aufgerufen.
Gruß,
kk
-
krümelkacker schrieb:
Yamakuzure schrieb:
Also stellt es sich jetzt so dar, dass das Verhalten einer
throw()-Spezifikation beibehalten wird, jedoch ohne den Overhead, der durch die Anforderungen derthrow(a,b,c)-Spezifikationen verursacht wird.Die Semantik von throw() und throw(a,b,c) wird sich nicht verändern.
Missverständnis: Ich sagte "das Verhalten einer", und meinte damit den neuen Operator.
Krümelkacker schrieb:
Yamakuzure schrieb:
Ansonsten klingt es für mich so, dass es zusätzlich die Möglichkeit geben soll, Funktionen dahingegend zu prüfen, ob sie garantiert nicht werfen, unabhängig davon, ob sie mit
noexceptspezifiziert sind oder nicht.So interpretiere ich das aber nicht. Ich finde die Formulierungen in den Proposals aber auch etwas komisch. Die Definition des noexcept-Operators sehe ich da als bindend an. Und diese Definition stützt sich klar auf die Ausnahme-Spezifizierungen bei Funktionsaufrufen. Nach meinem derzeitigen Verständnis, sieht es so aus:
void f(); // noexcept(f()) --> false void g() noexcept; // noexcept(g()) --> true void h() noexcept(true); // noexcept(h()) --> true void i() noexcept(false); // noexcept(i()) --> false void j() throw(); // noexcept(j()) --> true int k(int x) {return x+1;} // noexcept(k(2)) --> false void z() throw(int); // noexcept(z()) --> falseObwohl ein schlauer Compiler erkennen könnte, dass beim Aufruf k(3) offensichtlich keine Ausnahme fliegen kann, muss er dennoch false liefern, weil k keine "non-throwing exception specification" hat. In dieser Hinsicht, ist der Operator "konservativ". Die Ausnahmespezifizierungen dürfen aber nicht ignoriert werden, siehe 5.3.7 für die Definition des noexcept-Operators und 15.4/12 für die Definition von "non-throwing exception specification" im aktuellen Entwurf.
Naja, das entnahm ich aus:
The result of the noexcept operator is false if in an evaluated context the expression would contain
* a potentially evaluated call [Footnote: This includes implicit calls, e.g., the call to an allocation function in a new-expression. -- end footnote] to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification ([except.spec]),
* a potentially evaluated throw-expression ([except.throw]),
* a potentially evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check ([expr.dynamic.cast]), or
* a potentially evaluated typeid expression ([expr.typeid]) applied to an expression whose type is a polymorphic class type ([class.virtual]).Otherwise, the result is true.
Und noch ein Mißverständnis, denn das:
Krümelkacker schrieb:
Wenn der Operator true liefert, fliegt garantiert keine Ausnahme.
So, ich hoffe jetzt sind alle Klarheiten beseitigt.

-
Yamakuzure schrieb:
krümelkacker schrieb:
Yamakuzure schrieb:
Also stellt es sich jetzt so dar, dass das Verhalten einer
throw()-Spezifikation beibehalten wird, jedoch ohne den Overhead, der durch die Anforderungen derthrow(a,b,c)-Spezifikationen verursacht wird.Die Semantik von throw() und throw(a,b,c) wird sich nicht verändern. [...]
Missverständnis: Ich sagte "das Verhalten einer", und meinte damit den neuen Operator.
Verstehe ich immer noch nicht. Bist Du Dir sicher, dass Du das Wort "Operator" richtig verwendet hast? Oder meinstest Du die noexcept-Spezifikation? Jede Funktion, die mit noexcept, noexcept(true) oder throw() dekoriert wurde ist "non-throwing". Aber wir haben trotzdem noch den Unterschied in der Semantik: Im einen Fall führt eine Ausnahme, die aus einer non-throwing Funktion entfliehen will, zum Aufruf von std::terminate und in anderen Fall zu std::unexcepted.
Yamakuzure schrieb:
Krümelkacker schrieb:
Yamakuzure schrieb:
Ansonsten klingt es für mich so, dass es zusätzlich die Möglichkeit geben soll, Funktionen dahingegend zu prüfen, ob sie garantiert nicht werfen, unabhängig davon, ob sie mit
noexceptspezifiziert sind oder nicht.So interpretiere ich das aber nicht. [...]
Naja, das entnahm ich aus:
The result of the noexcept operator is false if in an evaluated context the expression would contain
- a potentially evaluated call [Footnote: This includes implicit calls, e.g., the call to an allocation function in a new-expression. -- end footnote] to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification ([except.spec]),
- a potentially evaluated throw-expression ([except.throw]),
- a potentially evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check ([expr.dynamic.cast]), or
- a potentially evaluated typeid expression ([expr.typeid]) applied to an expression whose type is a polymorphic class type ([class.virtual]).
Otherwise, the result is true.
Wie Du das daraus entnehmen kannst, verstehe ich nicht. Ich glaube eher, Du hast das mit den "implicit calls" missverstanden. Der Text macht doch deutlich, dass die Ausnahme-Spezifikation von potentiell aufgerufenen Funktionen sehr wohl eine Rolle spielt -- ob dieser Aufruf nun implizit passiert oder explizit, macht doch da keinen Unterschied. Mit dem Beispiel sollte das auch deutlich werden:
new int(23)enhält einen impliziten Aufruf zu einer Allozierungsfunktion, die nicht non-throwing deklariert wurde (sie kann ja eine bad_alloc Ausnahme werfen).
Yamakuzure schrieb:
Und noch ein Mißverständnis, denn das:
Krümelkacker schrieb:
Wenn der Operator true liefert, fliegt garantiert keine Ausnahme.
So, ich hoffe jetzt sind alle Klarheiten beseitigt.

Was ist damit?
kk
-
Oje... Ich kürze mal, ja?
krümelkacker schrieb:
Verstehe ich immer noch nicht.
Also: Wenn ich die Ausnahmespezifikation
throw()verwende, darf die Methode nicht werfen, oder es wirdunexpected()aufgerufen. Dieses ruft seinerseits, solange man den Handler nicht ändert,terminate()auf. Wenn ich die Ausnahmespezifikationnoexceptbzwnoexcept(true)verwende, darf die Methode nicht werfen, oder es wird sofortterminate()aufgerufen.Wenn ich das jetzt also richtig verstanden habe, dann ist das Verhalten beim Bruch der neuen
noexcept(true)-Spezifikation implizit das gleiche, dass auch beim Bruch derthrow()-Spezifikation ohne alternativenunexpected-Handler statfindet.Allerdings erlaubt die
noexcept-Spezifikation nur explizit "ja" und "nein", wodurch die zusätzliche Implementierungskomplexität derthrow(a,b,c)-Spezifikation wegfällt.Jetzt klar? Und wenn ja, habe ich das nun endlich richtig verstanden? Und wenn nein, wo ist mein Denkfehler?
Krümelkacker schrieb:
Wie Du das daraus entnehmen kannst, verstehe ich nicht. Ich glaube eher, Du hast das mit den "implicit calls" missverstanden.
Ich habe den Text so verstanden, dass
noexcept(foo())auch danntruezurückgibt, wenn foo() nichts weiter tut, als eine Funktion aufzurufen, die nicht werfend darf. Also in etwa so:void ichWerfNicht () noexcept { /* tue irgendwas absolut sicheres */ } void foo() { /* tue irgendwas absolut sicheres */ ichWerfNicht(); /* tue irgendwas absolut sicheres */ } /* irgendwo anders: */ bool wasBinIch = noexcept(foo()); // Muesste true ergeben ?!?Krümelkacker schrieb:
Was ist damit?
Damit ist, dass es ein unvollständiger Satz ist. Das Ende lautet:"ist das, was ich ausdrücken wollte." (Das kommt davon, wenn man es zu eilig hat. )
-
Yamakuzure schrieb:
Da das Verhalten bei einem Bruch der
noexcept-Spezifizierung allerdings doch weiter zur Laufzeit stattfindet, bleibt abzuwarten, ob uns Programmierern am Ende Möglichkeiten bleiben, darauf zu reagieren, oder ob kein Weg amterminate()vorbeiführt. (Mir schwant aber, dass es außer dem Setzen eines alternativen terminate-Handlers nichts mehr geben wird.)Du denkst falsch herum.
Aber erstmal: throw() muss unwind Code generieren auch wenn die Funktion garnicht werfen kann. Unwind Code ist teuer. noexcept braucht keinen Unwind Code.
noexcept ist ausserdem ein assert. Es ist kein if().
noexcept sagt: diese Funktion wirft nicht. Das ist das selbe wie wenn manvoid strcpy(char* trg, const char* src) { assert(trg); assert(src); while(*trg++=*src++) ; }sagt. Man will keine extremen Tests und dergleichen machen, sondern man faengt nur logikfehler ab die sich zur Testphase noch im Programm befinden.
Dass throw() dir defakto auch nicht mehr bringt lassen wir mal weg, das wird sonst zu kompliziert. Wenn also eine Exception eine noexcept Funktion verlaesst, dann ist das ein Logikfehler -> invarianten sind nicht korrekt. Du musst sowieso terminaten.
Das schoene an noexcept ist, dass es den Code schneller macht. Ein throw() macht ihn langsamer, ein noexcept macht ihn im idealfall schneller - meistens wohl aber gleichschnell wie eine Funktion ohne Spezifikation.
Deshalb ist noexcept soviel besser als throw(). Es geht einen anderen Weg: denke an assert, __assume, restrict,... noexcept gehoert zu dieser Gruppe.
Warum ein throw() sinnlos ist erklaere ich auch nochmal kurz:
Der unexcepted Handler hat keinen Kontext, du weisst also nicht was du werfen darfst. Sprich er bringt dir als Exception Translator nichts. Weiters hast du auch den Kontext des Fehlers nicht, du kannst also auf nichts reagieren. Alles was du machen kannst ist ein Shutdown der Anwendung (alles andere waere Wahnsinn). Du kannst ja nichtmal das Subsystem identifizieren dass den Fehler produziert hat. Du weisst nur: irgendwo sind Invarianten verletzt, ich kann meinen Daten nicht mehr trauen.throw() hat also Gegenueber noexcept hier keinen Vorteil. Denn so oder so, man ruft terminate() auf.
Nun mag throw() vielleicht ganz gut klingen solange es kein noexcept gibt. Aber es fungiert nur wie ein assert zur Laufzeit. Das kann ich besser haben mit StackGuards und uncaught_exception. Dann hat man naemlich ploetzlich den Kontext wieder.
Sprich: wenn ich Angst habe dass irgendwo eine exception fliegt wo keine fliegen sollte, dann ist throw() ein tragbares mittel. Aber: throw() ist Teil des Interfaces und das sollte ein assert eigentlich nicht sein. Prinzipiell bringt throw() also einige Nachteile gegenueber einem Stackguard mit sich - aber keinen Vorteil.
tl;dr: exception spezifikationen in C++ sind ein NoGo! noexcept dagegen macht Sinn.
zu:
void ichWerfNicht () noexcept { /* tue irgendwas absolut sicheres */ } void foo() { /* tue irgendwas absolut sicheres */ ichWerfNicht(); /* tue irgendwas absolut sicheres */ } /* irgendwo anders: */ bool wasBinIch = noexcept(foo()); // Muesste true ergeben ?!?Das ergibt false. Denn foo darf alles werfen was es will.
The result of the noexcept operator is false if in a potentially-evaluated context the expression would contain [..] a potentially evaluated call to a function [...] that does not have a non-throwing exception-specification
-
Yamakuzure schrieb:
Also: Wenn ich die Ausnahmespezifikation
throw()verwende, darf die Methode nicht werfen, oder es wirdunexpected()aufgerufen. Dieses ruft seinerseits, solange man den Handler nicht ändert,terminate()auf. Wenn ich die Ausnahmespezifikationnoexceptbzwnoexcept(true)verwende, darf die Methode nicht werfen, oder es wird sofortterminate()aufgerufen.Wenn ich das jetzt also richtig verstanden habe, dann ist das Verhalten beim Bruch der neuen
noexcept(true)-Spezifikation implizit das gleiche, dass auch beim Bruch derthrow()-Spezifikation ohne alternativenunexpected-Handler statfindet.Nicht ganz. Die "Stackunwinding"-Garantie fällt bei noexcept(true) weg.
Yamakuzure schrieb:
Jetzt klar? Und wenn ja, habe ich das nun endlich richtig verstanden?
Ich denke schon. Ich will aber auch selbst nicht ausschliessen, etwas falsch verstanden zu haben.
Yamakuzure schrieb:
Krümelkacker schrieb:
Wie Du das daraus entnehmen kannst, verstehe ich nicht. Ich glaube eher, Du hast das mit den "implicit calls" missverstanden.
Ich habe den Text so verstanden, dass
noexcept(foo())auch danntruezurückgibt, wenn foo() nichts weiter tut, als eine Funktion aufzurufen, die nicht werfend darf. Also in etwa so:void ichWerfNicht () noexcept { /* tue irgendwas absolut sicheres */ } void foo() { /* tue irgendwas absolut sicheres */ ichWerfNicht(); /* tue irgendwas absolut sicheres */ } /* irgendwo anders: */ bool wasBinIch = noexcept(foo()); // Muesste true ergeben ?!?Nein. f() ist ein "potentially evaluated call to a function [...] that does not have a non-throwing exception-specification". Daher liefert noexcept(f()) false. Alles andere wäre auch wahrscheinlich nicht wirklich praktikabel. Denk mal nur an Funktionsdeklarationen, die im Header auftauchen und in einer cpp-Datei definiert werden. Soll dann bei getrennter Übersetzung -- falls die Definition der Funktion in der aktuellen ÜE enthalten ist -- der noexcept Operator true liefern (wenn der Compiler feststellen kann, dass nichts fliegen kann) und in einer anderen ÜE false, weil nur die Deklaration bekannt ist? Wenn man diese Inkonsistenzen vermeiden will, kann man sich entweder auf die Funktions-Deklarationen und den Ausnahmespezifizierungen beschränken (so wie es im aktuellen Entwurf geschieht) oder man muss dem Compiler eine Art globale Analyse aufzwingen, falls er öfters mal true liefern will. Oder plädierst Du für inkonsistente noexcept-Ergebnisse, die davon abhängen, ob die Definition in der aktuellen ÜE bekannt ist? Das würde ich für eine schlechte Idee halten.
kk
-
@Krümelkacker: Ah! Danke nochmals!
@Shade: Das noexcept den Code schneller macht, halte ich ersteinmal für ein Gerücht, aber das es den Code nicht ausbremst wie die klassische Ausnahmespezifikation ist mir auch klar.
Was ich nicht verstehe ist, warum Ihr alle so scharf darauf seid auf Stack-Unwinding zu verzichten? Aber das führt langsam mal zu weit.
Mit _einer_ Sache bin ich aber eben nicht einverstanden:
Shade Of Mine schrieb:
Weiters hast du auch den Kontext des Fehlers nicht, du kannst also auf nichts reagieren. (...)Du weisst nur: irgendwo sind Invarianten verletzt, ich kann meinen Daten nicht mehr trauen.
Im Programm, hast du natürlich absolut Recht. Aber wenn man einen verbose terminate handler verwendet, hat man a) Stack-Unwinding, und b) den Namen der Exception. Und wenn du gerade in einer VM mit PuTTY auf einer AIX Maschine am anderen Ende der Welt unterwegs bist, dir nur VI mit zwei Farben (Kein Klickibunti, keine Ctags, nix) und IBM vacc zur Verfügung steht, es zwei Stunden nach Feierabend ist, morgen der Cut-Over stattfindet, und eines der Programme, die unbedingt laufen müssen aussteigt, dann freust du dich wie ein Schneekönig über einen Abbruch wie:
terminate called after throwing an instance of 'docIdNotFoundInTOA01'Warum? weil du dann sofort weißt, wo du nachschauen musst. Ein generelles catch auf std::exception bringt dir dann nämlich nichts, und du müsstest Wer-weiß-was anstellen um überhaupt erst einmal eine Idee zu Erhalten, wo du suchen musst.
Bevor du jetzt aber immer noch glaubst, ich versuche hier irgendwem weißzumachen, dass
throw()ganz toll undnoexceptganz schlecht sei (*), nein. Denn die obige Ausgabe habe ich mitnoexceptauch. Gut, ich habe dafür auch gleich noch eine unbestimmte Anzahl Speicherlecks dazu, aber das ist dann halt Pech des Entwicklungszyklus. Erkauft mit ein wenig Performance, auf die sich zahlende Kunden und die TÜV-IT ein Ei backen.(*) ...und das habe ich nun schon mehrfach betont.
-
Yamakuzure schrieb:
Was ich nicht verstehe ist, warum Ihr alle so scharf darauf seid auf Stack-Unwinding zu verzichten? Aber das führt langsam mal zu weit.
terminate() macht ja auch kein Stack Unwind. Stack Unwind ist oft eine ziemlich blöde Sache die man vermeiden will.
Im Programm, hast du natürlich absolut Recht. Aber wenn man einen verbose terminate handler verwendet, hat man a) Stack-Unwinding, und b) den Namen der Exception. Und wenn du gerade in einer VM mit PuTTY auf einer AIX Maschine am anderen Ende der Welt unterwegs bist, dir nur VI mit zwei Farben (Kein Klickibunti, keine Ctags, nix) und IBM vacc zur Verfügung steht, es zwei Stunden nach Feierabend ist, morgen der Cut-Over stattfindet, und eines der Programme, die unbedingt laufen müssen aussteigt, dann freust du dich wie ein Schneekönig über einen Abbruch wie:
terminate called after throwing an instance of 'docIdNotFoundInTOA01'Das ist mit StackGuards aber auch absolut kein Thema. Nur dass du Kontext hast.
Stell dir als Exception einfach OutOfBounds() vor. Da ist der Kontext essentiell viel Wert. Natürlich kann man auch mit terminate etwas herumtricksen, aber Prinzipiell kannst du dann einfach auch einen StackGuard verwenden und fährst besser.Mal davon abgesehen solche Logikfehler eh quasi nie auftreten. Die Probleme liegen idR woanders. Denn nothrow Funktionen sind idR so trivial dass es da keine Fehler mit wirft/wirft nicht gibt. Wir reden also von einer Fehlerquote jenseits von gut und böse...
Nochmals:
throw() bietet dir keine Vorteile. Du zählst zwar Sachen auf die throw() dir bietet, aber ein StackGuard bietet dir mehr, ist weniger Aufwand (kein komplexes Unwind im terminate()) und steht nicht im Interface der Funktion. Und das wichtigste: es garantiert dir Fehlerfreien Code.Stack Unwind im terminate kann Fatal sein. Deshalb würde ich es nie machen.
-
SOM schrieb:
Mal davon abgesehen solche Logikfehler eh quasi nie auftreten. Die Probleme liegen idR woanders.
*husthust* das war ein Beispiel aus meinem Arbeitsalltag. Schöööön das du weißt, das sowas nie auftritt. Aber der Keks, der vor X Jahren dieses bestimmte Programm geschrieben hat, war so lieb und hat für diese "Ausgabe" gesorgt, so dass ich den Krempel fertig bekommen habe.
Außerdem wüßte ich gerne, welches "StackGuard" du meinst? Den Protektor gegen Stack Smashing von Immunix, oder die Erweiterung zu gcc, die gegen Buffer Overflows schützen soll? Gibts noch andere Programme die so heißen? Und existieren die dann auch nicht für AIX+VACC? Klar, es gibt ProPolice/ssp für AIX. Aber das ist eine modifizierte Version von gcc und kein VACC mehr, der aber gefordert war. Und wie hätte mir ein SSP helfen sollen? Und wie hätte ich dem Kunden erklären sollen, er solle doch bitte alle Sicherheitskonzepte über den Haufen werfen, und mir ein paar hübsche Extraproggies installieren?
Aber weißt du was? Du hast Recht, ein solches Szenario gibt es nie und wird es nie geben, und ich habe meine Ruhe.
P.S.: Da es geflissentlich überlesen wurde, hier nochmals: Ich verwende niemals Ausnahmespezifikationen, außer es handelt sich um Bibliotheks- / ToolKit- oder FrameWork-Entwicklung. Und selbst da nur ganz ganz am Anfang um das Design zu prüfen.
-
Yamakuzure schrieb:
*husthust* das war ein Beispiel aus meinem Arbeitsalltag. Schöööön das du weißt, das sowas nie auftritt.
Habe nie behauptet dass es nie auftritt. Aber sei mal ehrlich: wie oft hattest du das Problem dass eine nothrow Funktion etwas geworfen hat? Das tritt so selten auf, dass es sich nicht lohnt ewig darueber zu diskutieren

Außerdem wüßte ich gerne, welches "StackGuard" du meinst?
Ich rede von C++ Code. Eine StackGuard Klasse oder ScopeGuard Klasse macht im Prinzip nichts anderes als im Dtor eine bestimmte Aktion ausfuehren.
Eine moegliche Implementierung koennte so aussehen:
class ForbidThrows { private: char const* function; char const* file; size_t line; public: ForbidThrows(char const* function, char const* file, size_t line) : function(function), file(file), line(line) {} ~ForbidThrows() { if(uncaught_exception()) { write_log("unallowed exception thrown", function, file, line); terminate(); } } }; #define FORBIDTHROWS ForbidThrows __guard(__FUNC__, __FILE__, __LINE__) void foo() { FORBIDTHROWS; throw "error"; }Alternativ wird auch oefters mal try/catch in einem Makro versteckt verwendet, ich mag das persoenlich weniger - aber es funktioniert auch.
Der Vorteil: du hast den Kontext den du beim unhandled exception handler nicht hast.
-
Shade Of Mine schrieb:
void foo() { FORBIDTHROWS; throw "error"; }...was, wenn foo mit noexcept deklariert worden wäre, nicht mehr garantiert zu der gewünschten Ausgabe führt. Nur mal so am Rande erwähnt, falls das jemandem noch nicht klar war.
-
krümelkacker schrieb:
...was, wenn foo mit noexcept deklariert worden wäre, nicht mehr garantiert zu der gewünschten Ausgabe führt. Nur mal so am Rande erwähnt, falls das jemandem noch nicht klar war.
Jep. Es ist ein Entweder/Oder und kein Und.
-
Shade Of Mine schrieb:
Eine moegliche Implementierung koennte so aussehen:
class ForbidThrows { private: char const* function; char const* file; size_t line; public: ForbidThrows(char const* function, char const* file, size_t line) : function(function), file(file), line(line) {} ~ForbidThrows() { if(uncaught_exception()) { write_log("unallowed exception thrown", function, file, line); terminate(); } } }; #define FORBIDTHROWS ForbidThrows __guard(__FUNC__, __FILE__, __LINE__) void foo() { FORBIDTHROWS; throw "error"; }Das ist gefährlicher Unsinn.
Beispiel: wir wollen Bar() so implementieren dass keine Exceptions geworfen werden können. Zur "Sicherheit" machen wir dein Makro rein:#include <iostream> #include <exception> class ForbidThrows { private: char const* function; char const* file; size_t line; public: ForbidThrows(char const* function, char const* file, size_t line) : function(function), file(file), line(line) {} ~ForbidThrows() { if(std::uncaught_exception()) { std::cout << "BOOM!" << std::endl; std::terminate(); } } }; #define FORBIDTHROWS ForbidThrows __guard(__FUNCTION__, __FILE__, __LINE__) // ------------------------------ void Bar() { FORBIDTHROWS; // code der keine Exceptions werfen kann } // bloss irgend eine klasse class SomeThing { public: ~SomeThing() { // nach dem Bar() "safe" ist, können wir es hier im dtor aufrufen ohne Probleme zu bekommen Bar(); } }; int main() { try { SomeThing thing; // hier jetzt Code der eine Exception werfen kann throw std::runtime_error("something happened"); } // BOOM! catch (std::exception const& e) { std::cout << e.what() << std::endl; } }Erklärung: Bar() wird von SomeThing::~SomeThing() aus aufgerufen, und SomeThing::~SomeThing wiederum läuft während eine noch-nicht-gefangene Exception rumfliegt. std::uncaught_exception() liefert also true, obwohl Bar() selbst keine Exception geworfen hat.
Dein Makro müsste also besser MAY_NOT_BE_CALLED_WHILE_UNWINDING heissen, denn sobald es in Code auftaucht der während Stack-Unwinding ausgeführt werden kann, knallt es.
Wer solche Konstrukte in seinen Programmen verwendet, sollte sich also darauf gefasst machen, dass seine Programme einfach so sterben gehen, obwohl nichts wirklich schlimmes passiert ist.
----
std::uncaught_exception() ist eine der C++ Funktionen die man am besten gleich wieder vergessen sollte, denn man kann damit nichts vernünftiges machen.
-
Hatte vergessen im Ctor den Status von uncaught_exception() zu überprüfen.
class ForbidThrows { private: char const* function; char const* file; size_t line; bool unwind; public: ForbidThrows(char const* function, char const* file, size_t line) : function(function), file(file), line(line), unwind(std::uncaught_exception()) {} ~ForbidThrows() { if(unwind != std::uncaught_exception()) { std::cout << "BOOM!" << std::endl; std::terminate(); } } };Ich habe leider die originalen Codes nicht bei mir, aber etwas in der Art verwende ich zum loggen solcher Fehler.
uncaught_exception ist etwas ziemlich praktisches - es ist verpönt weil viele Leute es als indikator verwenden ob sie werfen dürfen oder nicht.
Aber wie gesagt, wenn du das nicht willst, gibt es auch die Variante mit 2 markos:
#define NOTHROW_BEGIN try{ #define NOTHROW_END catch(...){log();terminate();} void foo() { NOTHROW_BEGIN; bla(); NOTHROW_END; }ist auch besser als throw() zu verwenden.
-
uncaught_exception ist etwas ziemlich praktisches - es ist verpönt weil viele Leute es als indikator verwenden ob sie werfen dürfen oder nicht.
Nein, uncaught_exception ist verpönt weil es konzeptionell kaputt ist!
Die Variante mit im Konstruktor prüfen funktioniert auch nicht richtig. Die ignoriert nämlich Fehler während des Stack-Unwinding komplett. Das kann's ja wohl auch nicht sein.
Damit man es für irgendwas vernünftig verwenden könnte müsste uncaught_exception die Exception-Nesting-Depth zurück geben. Tut es halt leider nicht.