Schnitstellen in C++?
-
Dravere schrieb:
1. Du solltest lesen, was Herb Sutter da geschrieben hat. Ich zitiere es gerne mal:
Stell Dir mal vor, das habe ich nicht zum erstenmal gelesen, und jedesmal aufs neue stelle ich fest, daß das ein ziemlicher Kappes ist. Denn es werden in erster Linie Argumente wegen kaputter Compiler oder suboptimaler Implementierung angeführt, und das kann beim besten Willen kein Argument sein die Sprache abzuändern.
Denn man kann sehr wohl einen Compiler schreiben, der erkennt, daß in einer Funktion keinerlei Code von Nöten ist, wenn in ihm ausschließlich throw() Funktionen benutzt werden. Insofern wäre dies ein Aufruf gewesen, die Compiler zu verbessern. Man will in Bezug auf throw() dieses Verhalten nun über ein anderes Sprachkonstrukt erreichen. Aber das ist schlecht. Es verkompliziert die Sprache weiter und macht alten Code kaputt (früher oder später), ohne dafür irgend einen triftigen Grund (bezüglich des Sprachdesigns) zu liefern. Schlußendlich macht man das nur, weil Compilerhersteller nicht willens sind ihre Compiler zu verbessern.
Dravere schrieb:
3. Ich würde aber anstatt
throw()zu nehmen, doch lieber folgendes machen, falls die Unsicherheit wirklich besteht.Damit wird eben gerade nicht dokumentiert, daß diese Klasse bei Destruktion keine Exception wirft. Die Eigenschaft "wirft nicht bei Destruktion" ist fundamental und Eigenschaft des Interfaces. Wenn es eine Verletzung des Interfaces gibt, dann hat es keinen Sinn das Programm weiterlaufen zu lassen, da schon längst der Zustand "undefined behavior" erreicht ist.
-
Was kann denn
noexceptmehr alsthrow(), abgesehen vom Operator?Und warum kann die Nothrow-Exceptionsicherheit über
noexceptnicht zur Kompilierzeit erzwungen werden, indemnoexcept-Funktionen keinethrow-Statements enthalten dürfen und nur anderenoexcept-Funktionen aufrufen können?
-
Dravere schrieb:
... uh, der letzte Satz gibt einem wirklich Kopfweh

Der auch.
-
Dravere schrieb:
Yamakuzure schrieb:
Dravere schrieb:
2. Man kann auch in der
mainein globalestry-catchmachen.Und genau das führt dazu, dass freeorion auf meinem Rechner mit "main() caught exception(std::exception): unregistered class" nicht aussteigt, sondern endlos in der Event-Schleife weiterläuft. Mit dieser Meldung wird niemand jemals den Fehler finden ohne im geistigen "Single-Step-Modus" durchlatschen zu müssen.
Tja, auch beim Werfen von Exceptions gilt es sinnvolle Informationen mitzuliefern. Oh Wunder, man könnte sogar Zeilennummer und File-Namen mitwerfen. Siehe die Makros
__FILE__und__LINE__.Gut, ich war ungenau. Ich meinte das, was du dann geantwortet hast, dass "einfach nur" try-catch in
main()nicht ausreicht. Nicht mehr, nicht weniger.Achja,"Fehler der FreeOrion"-Entwickler? Na, da stimme ich dir grundsätzlich zu. Die Exception kommt jedoch aus Boost, die hier zu wenig Information weiterreichen. Der Fehler der FO-Devs ist, die Ausnahme nicht da abzufangen, wo sie passiert. (Ersten beiden Boost-users, das dritte ist schon FreeOrion, und dieser Thread ist auf Platz 5.) Alles hat zwei Seiten.
Draveres schrieb:
Du entfernst die Spezifikation später sogar? Das ist doch ein riesiger Aufwand.
Nein. Es sind zwei Templates, eines mit 33 und eines mit 53 Methoden die eine
throw-Spezifikation hatten. Alle Methoden wurden durch mein Testprogramm mit (teils unsinnigen) Werten getestet, keine Methode wirft eine Ausnahme, die sie nicht werfen soll, und das Entfernen hat mich zwei Minuten gekostet. (Plus einer um zwei dadurch entstandener Fehler dank Zeilenumbrüche zu beheben) (*)Draveres schrieb:
Während mein Programm in der Entwicklungsphase ist, starte ich es immer mit einem Debugger.
Die Design-Phase ist bei mir grundsätzlich fertig, wenn mein UML fertig ist. Da gibts dann noch keine Zeile Code. In der Entwicklungsphase starte ich den Debugger nur, wenn es unbedingt nötig ist. Ein Programm, und ein ToolKit umso mehr, muss selber mit Fehlern umgehen können. Wenn es das dann nicht kann, ergibt das ein FreeOrion-Erlebnis. (Das Beispiel _ist_ jetzt aber doch leicht überstrapaziert, nicht wahr? ;))
Letztendlich ist es aber Geschmackssache ob man immer im Debugger startet oder nur bei Bedarf.~john schrieb:
Denn es werden in erster Linie Argumente wegen kaputter Compiler oder suboptimaler Implementierung angeführt, und das kann beim besten Willen kein Argument sein die Sprache abzuändern.
Da hast du natürlich Recht. In dem zitierten Artikel geht auch darum, das scheint gerne "überlesen" zu werden, dass Ausnahmespezifikation gerne mit falschen Annahmen eingebaut werden. (Ich habe den Teil oben zitiert.)
Jedoch ist das von "Krümelkacker" beschriebene Verhalten der neuen noexcept-Operatoren das, was normalerweise (fälschlicherweise) vonthrow()angenommen wird, und daher freue ich mich auf die Einführung.Allerdings haben wir nicht 1998/99, und ich, so sehr ich mich auf C++0x freue, werde nicht mit dem gleichen Elan darangehen wie damals. War schon Krass, C++98. Endlich Exception-handling (Yay!), endlich STL (Yay!), endlich sinnvoller Umgang mit dem
newoperator (Yay!). Und nach dem "Draufstürzen", kam das "Niederstürzen". Die Umsetzung verlief schleppend (VC++, BorlandC++) oder garnicht (StormC++), und selbst in Quelltexten von 2003 (Kommerzell, wohlgemerkt) fand ich noch wunderschöne Konstrukte wie:if (!(member = new foo())) log_error();Das kann echt frustrierend sein... Aber ich schweife wie wild ab, was beweist, das Bremen Vs Mailand derzeit nicht sehr spannend ist.

(*)
Spezis raus aus den Deklarationen:$ perl -p -i.bak -e 's/(\) |const )\s*throw\([^)]*\)(\s*)/$1$2/mig' T*.hSpezis raus aus den Definitionen:
$ perl -p -i.bak -e '^throw\([^)]*\)\s*$//mig' T*.hEinmal astyle drüberlaufen lassen, feddich!

-
Nexus schrieb:
Was kann denn
noexceptmehr alsthrow(), abgesehen vom Operator?Die Semantik ist etwas anders.
void wirft_immer_ausnahme(); void f() throw() { std::string dings = "hello"; wirft_immer_ausnahme(); // #1 } void f() noexcept { std::string bums = "world!"; wirft_immer_ausnahme(); // #2 }#1 führt dazu, dass dings ordnungsgemäß zerstört wird und anschließend std::unexpected ausgeführt wird.
#2 führt dazu, dass std::terminate aufgerufen wird. Dabei bleibt es dem Compilerhersteller überlassen, ob bums zerstört wird. Er muss aber das Verhalten dokumentieren (implementation-defined).
Die etwas andere Semantik von noexcept soll sich angeblich positiv auf die Laufzeit- und Speicherplatz-Effizienz auswirken (Quelle: Meine Erinnerung).
Nexus schrieb:
Und warum kann die Nothrow-Exceptionsicherheit über
noexceptnicht zur Kompilierzeit erzwungen werden, indemnoexcept-Funktionen keinethrow-Statements enthalten dürfen und nur anderenoexcept-Funktionen aufrufen können?Dann bräuchtest Du auch wieder einen Fluchtmechanismus (ähnlich wie const_cast bzgl const-correctness). Wenn Du zB genau weißt, dass eine bestimmte Bibliotheksfunktion nie eine Ausnahme wirft, sie aber nicht mit noexcept dekoriert wurde und Du sie innerhalb einer noexcept-Funktion aufrufen willst, würde das nach Deinem Vorschlag nicht mehr kompilieren. Dazu kommt noch, dass die Ausnahmespezifizierungen nur halbherzig Teil des Typsystems sind, was die Verwendung von Funktionszeigern in noexcept-Funktionen mindestens verkompliziert.
Compile-Zeit-Meldung diesbezüglich sind Sache der Implementierungsqualität. Ein Compiler könnte zumindest eine gescheite Warnung ausgeben, wenn man so etwas baut:
void yay() noexcept { throw 1729; }kk
-
krümelkacker schrieb:
Die Semantik ist etwas anders. [...] Die etwas andere Semantik von noexcept soll sich angeblich positiv auf die Laufzeit- und Speicherplatz-Effizient auswirken.
Vielen Dank für die Erklärung.
krümelkacker schrieb:
Dann bräuchtest Du auch wieder einen Fluchtmechanismus (ähnlich wie const_cast bzgl const-correctness).
Das ist mir vorher auch in den Sinn gekommen, spontan hätte ich den Aufruf von werfenden Funktionen innerhalb von
try-Blöcken mit anschliessendemcatch(...)erlaubt. Ist natürlich nicht gross durchdacht. Klar, man würde die Komplexität der Sprache durch einen zusätzlichen propagierenden Sicherheitsmechanismus nebenconstundvolatilestark erhöhen, zudem wäre bisheriger Code sozusagen nicht "noexcept-korrekt". Das ist schon nicht gerade schön.Auf der anderen Seite habe ich momentan wie ~john etwas das Gefühl,
noexceptsei stattdessen die Halbwegs-Lösung, weil ein richtiger Mechanismus doch zu viel wäre, aber man vom bösenthrow()wegkommen möchte. Ich weiss nicht, was ich davon halten soll...
-
Ich habe einen ganz einfachen Grund, keine Exception-specifications zu verwenden: Wir schreiben (auch) für MS-Compiler (vorgeschrieben von der Firma), die die Teile komplett ignorieren.
Gruß,
Simon2.
-
@Simon2: Das ist in der Tat ein guter Grund.
Also ersteinmal guten Morgen, und vielfach sorry! Ich habe wirklich nicht damit gerechnet mit einer Zwischenfrage eine solche Diskussion loszutreten.
@Gilli82: Wurde deine frage eigentlich zu deiner Zufriedenheit beantwortet?
Und dann war da noch...
Tut mir leid, dass ich da nicht dran gedacht habe. Es war mir ehrlich gesagt entfallen, da ich in dem von mir beschriebenen Beispiel tatsächlich einen Nutzen austerminate()hatte.
Es ist jedoch so, dass es einen durchaus bemerkenswerten Nutzen der Ausnahmespezifikation gibt, der bei einer nicht-gelisteten Ausnahme keinterminate()hervorruft. Nach dem, was Simon2 geschrieben hat, gehe ich aber davon aus, dass es etwas gcc-Spezifisches ist.Im Header "exception" von gcc-4.4.4 findet sich der folgende Schnippsel, den ich mal so zitiere:
/** If an %exception is thrown which is not listed in a function's * %exception specification, one of these may be thrown. */ class bad_exception : public exception { public: bad_exception() throw() { } // This declaration is not useless: // http://gcc.gnu.org/onlinedocs/gcc-3.0.2/gcc_6.html#SEC118 virtual ~bad_exception() throw(); // See comment in eh_exception.cc. virtual const char* what() const throw(); };...bringt aber nix, wenn man bad_exception nirgendwo auffängt.

-
krümelkacker schrieb:
#1 führt dazu, dass dings ordnungsgemäß zerstört wird und anschließend std::unexpected ausgeführt wird.
#2 führt dazu, dass std::terminate aufgerufen wird. Dabei bleibt es dem Compilerhersteller überlassen, ob bums zerstört wird. Er muss aber das Verhalten dokumentieren (implementation-defined).
Tut mir leid, aber das neue Verhalten ist definitiv schlechter als das alte. In erster Linie muß ein Programm formal korrekt sein. Dieser ganze "undefined behavior" und "implementation defined" Scheiß braucht kein Mensch. Jetzt ein weiteres Sprachkonstrukt einzubauen, welches dieses Verhalten zeigt verschlimmbessert eindeutig die Situation.
-
~john schrieb:
krümelkacker schrieb:
#1 führt dazu, dass dings ordnungsgemäß zerstört wird und anschließend std::unexpected ausgeführt wird.
#2 führt dazu, dass std::terminate aufgerufen wird. Dabei bleibt es dem Compilerhersteller überlassen, ob bums zerstört wird. Er muss aber das Verhalten dokumentieren (implementation-defined).
Tut mir leid, aber das neue Verhalten ist definitiv schlechter als das alte. In erster Linie muß ein Programm formal korrekt sein. Dieser ganze "undefined behavior" und "implementation defined" Scheiß braucht kein Mensch. Jetzt ein weiteres Sprachkonstrukt einzubauen, welches dieses Verhalten zeigt verschlimmbessert eindeutig die Situation.
Ja, wenn das tatsächlich stimmt, was KK da geschrieben hat, wäre das in der Tat eine deutliche Verschlechterung der aktuellen Situation. Aktuell gibt es schließlich
set_unexpected(),set_terminate()undatexit(), mit deren Hilfe man schon deutlich praktische Sachen über die Ausnahmespezifikationen machen kann. Und selbst wenn man es nicht tut, gibt es immer die Pflicht zum Stack-Unwinding. Wenn die wegfallen sollte, dann gute Nacht.Immerhin steht das Verhalten der Ausnahmespezifikation, wie zum Beispiel in gcc implementiert, auch so im Standard. (Siehe ISO/IEC 14882:1998, Abschnitt 15.4, Punkte 8 und 9). Den Standard zu kritisieren, weil ein bestimmter Compilerhersteller selbigen nicht umsetzen mag, geht garnicht.
-
"Deprecating Exception Specifications" (2010-03-12)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3051.html"Working draft, Standard for Programming Language C++" (2010-08-21)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3126.pdf
-
Also der erste Link auf ein "Proposal" ist nicht sehr nütlich. Viel interessanter finde ich da die Stellungnahmen von Standards-Meeting, März 2010:
Report auf herbsutter.comSteve Adamczyk schrieb:
We discussed two things related to exception specifications: noexcept (N3050), and deprecating exception specifications (N3051). There was some spirited discussion of what happens when you violate a promise not to throw. In the end, we decided that in such a circumstance terminate() gets called, and it’s unspecified whether any cleanup is done for local variables between the throw point and the point of the violated noexcept. It’s believed that for efficient implementations of exception handling a noexcept will not add any overhead and will not restrict optimization opportunities.
The old-style exception specifications, including the throw() form, will now be deprecated. The library specification needs to be updated to use noexcept instead, and that won’t happen at this meeting.
Herb Sutter schrieb:
Note that the decision to call terminate() is not final and alternatives will be considered at future meetings. There was general (but not universal) agreement that the program should not be allowed to continue executing if a noexcept is violated because in that case the program will almost certainly be in a corrupt state, and so the consensus was in favor of guaranteeing to call terminate() instead of allowing arbitrary undefined behavior, but it’s possible that a comfortable middle ground between those two options may yet be found.
Das PDF ist "nur" ein Working-Draft und kann, wie Herb Sutters Kommentar oben zeigt, nicht als "Garantie" bewertet werden.
Na, bis März ist ja noch etwas hin.

-
Yamakuzure schrieb:
Das PDF ist "nur" ein Working-Draft und kann, wie Herb Sutters Kommentar oben zeigt, nicht als "Garantie" bewertet werden.
Herb Sutter hat sich im März 2010 damit sicherlich nicht auf das/den "working draft" vom August 2010 beziehen wollen. Aktueller ist sein letzter Report, in dem es auch wieder um noexcept geht:
noexcept part1: Destructors noexcept by default [...]
noexcept part2: noexcept will be applied to the standard library [...]
noexcept part3: terminate stays. It was reaffirmed that if you violate a noexcept specification, your program will call std::terminate and not continue execution in what would be a corrupt state. Stack unwinding is not required.Das "working draft"-PDF entspricht diesem Stand. Klar, das könnte sich immer noch ändern, aber ich denke nicht, dass das noch passieren wird.
kk
-
Yamakuzure schrieb:
Ja, wenn das tatsächlich stimmt, was KK da geschrieben hat, wäre das in der Tat eine deutliche Verschlechterung der aktuellen Situation.
Nein.
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.
Sprich: throw() bringt nie etwas. Ich kenne keine Situation wo throw() einen Vorteil gegenüber keiner Spezifikation bringt.
noexecpt dagegen bringt etwas: der Code wird kleiner und schneller.
noexcept ist nicht dafür da bei einem mightThrow() ein terminate() zu erzwingen. noexcept ist wie restrict in C99 oder ein __assume im VC++. Es gibt eine Garantie ab auf die man sich verlassen kann. Das ist Sinnvoll.
Sprich:
throw() ist niemals Sinnvoll (es gibt viele Situationen wo es nicht schadet - aber Schaden und Vorteil sind unterschiedliche Sachen).
noexcept ist in bestimmten Situationen sinnvoll.
-
Shade Of Mine schrieb:
throw() bringt dir defakto nämlich garnichts. So oder so fliegt eine Exception die du nicht erwartest.
...und vom unexpected()-Handler bearbeitet werden kann...
Shade Of Mine schrieb:
Nur dass dir bei bei einem throw() im Gegensatz zu keiner Spezifikation die Exception Daten verloren gehen.
...außer a) es wird ein verbose_terminate_handler genutzt, der diese Daten ausgibt, oder b) eine eigene bad_exception Klasse benutzt, die diese Daten weiter transportiert, oder c) ein unexpected()-Handler benutzt, der die Daten weiterverwendet, oder eine Kombination daraus.
Shade Of Mine schrieb:
Sprich: throw() bringt nie etwas. Ich kenne keine Situation wo throw() einen Vorteil gegenüber keiner Spezifikation bringt.
und trotzdem sind Standardbibliotheken voll davon.
Shade Of Mine schrieb:
noexecpt dagegen bringt etwas: der Code wird kleiner und schneller.
Und garantiert, dass auch wirklich keine Weiterverwendbarkeit besteht, da garantiert terminate() statt unexpected() aufgerufen wird. Außerdem garantiert es Speicherlecks. Tolle Verbesserung für was? Achja. Performance.
Shade Of Mine schrieb:
noexcept ist nicht dafür da bei einem mightThrow() ein terminate() zu erzwingen. noexcept ist wie restrict in C99 oder ein __assume im VC++. Es gibt eine Garantie ab auf die man sich verlassen kann. Das ist Sinnvoll.
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.
Shade Of Mine schrieb:
Sprich:
throw() ist niemals Sinnvoll (es gibt viele Situationen wo es nicht schadet - aber Schaden und Vorteil sind unterschiedliche Sachen).
noexcept ist in bestimmten Situationen sinnvoll.Nein, das sehe ich umgekehrt. Wenn die Änderungen am Standard wirklich dazu führen, dass die jetzige Flexibilität wegfällt auf eine Spezifikationsverletzung zu reagieren, dann ist noexcept, außer bei Destruktoren, niemals Sinnvoll.
Aber als kleine Abkürzung, damit nicht die nächste, bereits getätigte, Diskussion losbricht:
Ausnahmespezifikationen sind in den meisten Projekten unnötig. Wenn man aber ein Toolkit oder ein Framework, oder irgend etwas Anderes, was auf zur Entwicklungszeit unbekannte Weise weiterverwendet werden können soll, entwickelt, dann sieht das Anders aus.
Dort ist es sehr wichtig, dass Funktionen und Methoden ausschließlich die Ausnahmen werfen, die Dokumentiert sind. Da Funktionen aber gerne andere Funktionen aufrufen, ist es keine Selbstverständlichkeit, die möglichen "Ketten" immer sauber dokumentiert zu bekommen.
Während der Grundentwicklung sind nun diese Ausnahmespezifikationen recht hilfreich, denn wenn dir dein Testprogramm für eine Bibliothek um die Ohren fliegt, weil eine andere Ausnahme geworfen wird, als du dokumentiert hast, dann weißt du, dass da etwas falsch ist, und du nachbessern musst.
Wenn du einfach alles durchlässt, werden dir und deinem ersten Kunden/Nutzer/Chef/Wemauchimmer womöglich Ausnahmen aus Bereichen, die diese nicht werfen sollten, um die Ohren fliegen. Davon ganz abgesehen, dass das sehr leicht zu einem "Unhandled Exception"-Abbruch führt, ist es sehr sehr schwer diese Stellen im Nachhinein zu finden.
-
Shade Of Mine schrieb:
noexcept ist nicht dafür da bei einem mightThrow() ein terminate() zu erzwingen. noexcept ist wie restrict in C99 oder ein __assume im VC++. Es gibt eine Garantie ab auf die man sich verlassen kann. Das ist Sinnvoll.
Yup.
-
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