Schnitstellen in C++?



  • Irgendwie ist es schwierig das zu beschreiben.

    Also ich versuche das mal anhand eines Falles, den ich vorgestern hatte, wo die Spezifikation mit terminate() ein Glücksfall war:
    Eine Methode setzt einen internen Zeiger auf ein Element einer doppelt verketten Liste, und tut dieses je nach Index, der übergeben wird. Die übergebene Zahl wird vorher normalisiert, so dass sie auf jeden Fall "0 <= index < size()" ist.
    Die benannte Methode schlägt niemals fehl, es sei denn, es gibt keine Elemente. In diesem Fall wirft sie eine spezielle Ausnahme, die nur von dieser einen Methode geworfen werden kann.
    Ich habe eine andere Methode mit throw() markiert. Diese sollte überprüfen, ob es in der Liste ein per Argument übergebenes Element gibt. (Ein kleiner Vorgriff: Ich hatte in genau dieser Methode vergessen den Fall, dass die Liste leer ist, abzufangen. Da dieser Fall aber abgefangen hätte werden müssen, hatte ich auf einen try-catch-Block verzichtet.) Diese Methode geht nun die Liste durch, und muss dafür jedes Element bis zum Ende, oder bis zu einem Treffer überprüfen. Hierfür wird die oben beschriebene Methode in einer Schleife verwendet.

    Mit der Technik des globalen try-catch-Blocks (den ich übrigens auch verwende) hätte ich nur die Aussage erhalten, das eine Ausnahme vom Typ der abgefangen Klasse (Im Zweifelsfall std::exception) aufgetreten ist. Der Nutzen wäre gleich null gewesen. Diese Ausnahme kommt immer aus der gleichen Methode, keine sonstwie übergebenen Informationen hätte irgendeine Hilfe sein können.

    Mit der terminate()-Methode hatte ich dagegen genau zwei Aussagen:

    1.: Es handelt sich um genau diese spezielle Ausnahme, von wo die geworfen wird, war mir als Entwickler natürlich aufgrund ihrer Natur klar.
    2.: Die Ausnahme trat an einer Stelle auf, wo sie nicht auftreten durfte.

    Nun handelt es sich bei dem Programm um ein Testprogramm für eben dieses zu entwickelnde Toolkit. Und wie das bei Testprogrammen so üblich ist (sein sollte), hat es natürlich vor dem Abbruch genau angemerkt, was geschehen soll.
    So war es für mich ein Leichtes den Fehler zu finden, denn der einzige "Übeltäter" konnte die mit throw() markierte Methode sein, die es natürlich auch war.
    Am Ende wird es nicht mehr zu einem terminate() kommen können. Zum Einen weil das Testprogramm mir ermöglichen soll alle "Störstellen" zu finden, zum Anderen weil die Ausnahmespezifikationen am Ende wieder entfernt werden.

    --- cut ---
    Es handelt sich hierbei, _natürlich_, um einen Sonderfall. Ich versuche auch nicht irgendwem weißzumachen, das Ausnahmespezifikation was ganz Tolles wären. Sind sie nicht. Bei einer normalen Applikation würde ich sie eher vermeiden wollen, da sie die Flexibilität einschränken.
    Es geht mir nur darum, dass es gerade bei einer so vielfältigen Sprache wie C++ eher ungünstig ist, solche Pauschalaussagen wie "Ist nutzlos" zu tätigen. Vor allem wenn ich den Gegenbeweis "Bunt auf weiß" in meiner IDE vor Augen habe. 😉
    Zusätzlich habe ich mit dem FreeOrion-Beispiel auch noch den Gegenbeweis zu "du brauchst nur catch in main()", diesmal "weiß auf schwarz".


  • Administrator

    Yamakuzure schrieb:

    Mit der Technik des globalen try-catch-Blocks (den ich übrigens auch verwende) hätte ich nur die Aussage erhalten, das eine Ausnahme vom Typ der abgefangen Klasse (Im Zweifelsfall std::exception) aufgetreten ist. Der Nutzen wäre gleich null gewesen. Diese Ausnahme kommt immer aus der gleichen Methode, keine sonstwie übergebenen Informationen hätte irgendeine Hilfe sein können.

    1. Wenn du nützliche Informationen in der Exception verpacken würdest, hätte es durchaus einen Mehrwert gehabt. Man muss ja nicht nur ein catch(...) durchführen in der main. Man kann ja auch die zahllosen verschiedenen Basis-Exception abfangen, welche man verwendet oder sogar ganz anderes zeug. Wenn also sinnvolle Informationen in der Exception vorhanden wären, hättest du auch die nötigen Informationen in der main gehabt.
    2. Ein Debugger hätte dir diese Informationen sofort geliefert und gerade dazu sind Debugger auch da.

    Yamakuzure schrieb:

    Es geht mir nur darum, dass es gerade bei einer so vielfältigen Sprache wie C++ eher ungünstig ist, solche Pauschalaussagen wie "Ist nutzlos" zu tätigen. Vor allem wenn ich den Gegenbeweis "Bunt auf weiß" in meiner IDE vor Augen habe.

    Ausnahmespezifikationen sind in der aktuellen Form ziemlich nutzlos. Nicht ohne Grund nimmt man sie aus dem Standard raus. Ich kann mich nur wiederholen, es gibt deutlich besser Alternativen, für das was du erreichen möchtest.

    Yamakuzure schrieb:

    Zusätzlich habe ich mit dem FreeOrion-Beispiel auch noch den Gegenbeweis zu "du brauchst nur catch in main()", diesmal "weiß auf schwarz".

    Das ist doch kein Gegenbeweis. Das ist nur ein Beweis, dass ihre Fehlermeldungen schlecht sind und allenfalls die mitgelieferten Fehlerinformationen.

    Grüssli



  • Zu 1.:
    Du liest den Text nicht. _Meine_ exceptions tragen die Informationen was _wo_ passiert ist mit.(*) In dem Beispiel hätte diese Information aber nur ausgesagt, was ich eh schon wusste, da genau diese Exception an nur einer einzigen Stelle vorkommt. Aber die Methode, die diese Ausnahme nicht abgefangen hat, was ein Fehler war, hätte sie unbedingt abfangen _müssen_, da diese spezielle Methode _niemals_ fehlschlagen darf. Ich musste aber nicht herausfinden _das_ eine Ausnahme aufgetreten ist, sondern _wo_ sie _nicht_ aufgefangen wurde.

    Und in main() steht:

    try
      {
        /* alle möglichen tests */
      }
    catch (pwx::mrf::ExceptionBase &e) { /* info handling */ }
    catch (std::exception &e) { /* sonstige exception infos */ }
    catch (...) { /* Panik! */ }
    

    (*) Gespeichert wird: Objektname, Methodenname, Datei, Zeile, Beschreibung, relevante Daten. Aber das reicht nun einmal nicht immer.

    Zu 2.: Ja, ich weiß. Ich verwende gdb sehr intensiv, da er sehr gut in Code::Blocks integriert ist.

    Zum "Gegenbeweis": Es ist der Beweis dafür, dass die Pauschalaussage, das try-catch in main() alle Probleme pauschal löst, falsch ist. Man muss schon ein wenig mehr tun, wie du selber ja sagst, gell? 😉



  • theta schrieb:

    Herb Sutter im C++ Users Journal zu Exception Specifications:
    http://www.gotw.ca/publications/mill22.htm

    Auch wenn ich Herb Sutters Ausführungen zu C++ als sehr hochwertig betrachte, ist dies in Bezug auf Exceptions definitiv nicht der Fall. Es gibt in C++ keinerlei logischen Grund einen Destruktor nicht als throw() zu definieren, da Exception werfende Destruktoren unweigerlich zu Problemen führen, und früher oder später eine zweite Exception geworfen wird -> terminate(). Da aber in Falle eines werfenden Destruktors das ganze nicht mehr deterministisch passiert, ist der Fehler faktisch schwer zu finden. Daher sollten Destruktoren immer throw() definiert sein.

    Es kann doch nicht sein, daß man eine Programmsprache nach den mangelhaften Compiler entwirft. Wenn der Compiler kaputt ist, sollte man ihn reparieren und nicht die Sprache an den kaputten Compiler anpassen.


  • Administrator

    Yamakuzure schrieb:

    Du liest den Text nicht.

    Doch, tue ich. Ich möchte dich also bitten, wenn ich mir schon die Zeit nehme, deine Beiträge durchzulesen, dass du mir nicht unterstellst, dass ich dies nicht tue. Ich habe einfach deinen Text missverstanden. Wahrscheinlich weil er etwas komplex ist, um einen simplen Sachverhalt zu erklären.

    Man hätte nämlich sagen können, dass bei einem globalen try-catch in der main , der Callstack fehlt. Das ist grundsätzlich schon alles, was du kritisierst. Aber wie gesagt, dazu gäbe es ja den Debugger.

    Aber den benutzt du ja anscheinend auch:

    Yamakuzure schrieb:

    Zu 2.: Ja, ich weiß. Ich verwende gdb sehr intensiv, da er sehr gut in Code::Blocks integriert ist.

    Dann frage ich mich immer noch, wieso du die throw -Spezifikation verwendest. Mit dem Debugger hättest du diese Informationen schliesslich auch problemlos erhalten.

    Yamakuzure schrieb:

    Zum "Gegenbeweis": Es ist der Beweis dafür, dass die Pauschalaussage, das try-catch in main() alle Probleme pauschal löst, falsch ist. Man muss schon ein wenig mehr tun, wie du selber ja sagst, gell? 😉

    Ich habe dies so nicht gesagt. Nicht mal annähernd. Ich wollte eine Alternative Vorschlagen, welche in vielen Fällen sehr praktisch sein kann. Als eine pauschale Lösung für alle Probleme, habe ich dies nie beworben.

    Grüssli


  • Administrator

    ~john schrieb:

    Auch wenn ich Herb Sutters Ausführungen zu C++ als sehr hochwertig betrachte, ist dies in Bezug auf Exceptions definitiv nicht der Fall. Es gibt in C++ keinerlei logischen einen Destruktor nicht als throw() zu definieren, da Exception werfende Destruktoren unweigerlich zu Problemen führen, und früher oder später eine zweite Exception geworfen wird -> terminate(). Da aber in Falle eines werfenden Destruktors das ganze nicht mehr deterministisch passiert, ist der Fehler faktisch schwer zu finden. Daher sollten Destruktoren immer throw() definiert sein.

    1. Du solltest lesen, was Herb Sutter da geschrieben hat. Ich zitiere es gerne mal:

    Moral #2: Except possibly an empty one, but if I were you I’d avoid even that.

    Boost’s experience is that a throws-nothing specification on a non-inline function is the only place where an exception specification “may have some benefit with some compilers” [emphasis mine]. That’s a rather underwhelming statement in its own right, but a useful consideration if you have to write portable code that will be used on more than one compiler platform.

    It’s actually even a bit worse than that in practice, because it turns out that popular implementations vary in how they actually handle exception specifications. At least one popular C++ compiler (Microsoft’s, up to version 7.x) parses exception specifications but does not actually enforce them, reducing the exception specifications to glorified comments. But, on the other hand, there are legal optimizations a compiler can perform outside a function, and which the Microsoft 7.x compiler does perform, that rely on the ES enforcement being done inside each function -- the idea is that if the function did try to throw something it shouldn’t the internal handler would stop the program and control would never return to the caller, so since control did return to the caller the calling code can assume nothing was thrown and do things like eliminate external try/catch blocks. So on that compiler, because the checking is not done but the legal optimization that relies on it is done, the meaning of “throw()” changes from the standard “check me on this, stop me if I inadvertently throw” to a “trust me on this, assume I’ll never throw and optimize away.” So beware: If you do choose to use even an empty throw-specification, read your compiler’s documentation and check to see what it will really do with it. You might just be surprised. Be aware, drive with care.

    Er schliesst gerade throw() nicht vollständig aus. Mahnt aber zur Vorsicht.

    2. Ein Destruktor von einem Stackobjekt oder einem Objekt mit statischer Lebensdauer, welcher eine Exception wirft, löst automatisch ein terminate aus.

    3. Ich würde aber anstatt throw() zu nehmen, doch lieber folgendes machen, falls die Unsicherheit wirklich besteht.

    try
    {
      // ...
    }
    catch(...)
    {
      std::terminate();
    }
    

    Ich konnte sowas aber bisher By-Design ausschliessen.

    Grüssli



  • Dravere schrieb:

    Yamakuzure schrieb:

    Du liest den Text nicht.

    Doch, tue ich. Ich möchte dich also bitten, wenn ich mir schon die Zeit nehme, deine Beiträge durchzulesen, dass du mir nicht unterstellst, dass ich dies nicht tue. Ich habe einfach deinen Text missverstanden. Wahrscheinlich weil er etwas komplex ist, um einen simplen Sachverhalt zu erklären.

    Entschuldige bitte. Ich bin frustriert, weil ich mich irgendwie nicht verständlich machen kann. Außerdem bekomme ich langsam Kopfschmerzen. (Was aber an der Arbeit liegt, nicht am Forum)

    Dravere schrieb:

    Dann frage ich mich immer noch, wieso du die throw -Spezifikation verwendest. Mit dem Debugger hättest du diese Informationen schliesslich auch problemlos erhalten.

    Ich benutze den Debugger bei Bedarf, und in diesem Fall habe ich ihn nicht gebraucht, da in diesem sehr speziellen Fall die terminate() -Ausgabe bereits sofort gezeigt hat, was schief war. Die Spezifikationen werden ab einem bestimmten Stand des Projekts auch wieder entfernt, und normalerweise benutze ich sie garnicht.

    Ich habe übrigens nie behauptet, dass du main->try->catch als ultimative lösung beworben hättest, ich habe nur gesagt, dass es keine allgemeingültige Lösung ist, und das du mir darin bereits zugestimmt hast. 😉

    @~john:
    Ja. Der Destruktor darf niemals eine Ausnahme werfen, schließlich könnte der Destruktor gerade in Folge einer Ausnahme im Rahmen des Stackunwindings aufgerufen werden. Würde er nun eine Ausnahme werfen, gäbe es keine Möglichkeit mehr darauf zu reagieren. Tatsächlich würde sofort terminate() aufgerufen.


  • Administrator

    Yamakuzure schrieb:

    Ich benutze den Debugger bei Bedarf, und in diesem Fall habe ich ihn nicht gebraucht, da in diesem sehr speziellen Fall die terminate() -Ausgabe bereits sofort gezeigt hat, was schief war. Die Spezifikationen werden ab einem bestimmten Stand des Projekts auch wieder entfernt, und normalerweise benutze ich sie garnicht.

    Du entfernst die Spezifikation später sogar? Das ist doch ein riesiger Aufwand. Wieso startest du das Programm nicht immer mit dem Debugger? Während mein Programm in der Entwicklungsphase ist, starte ich es immer mit einem Debugger. Es ist eher die Ausnahme, wenn ich es ohne Debugger starte.

    Yamakuzure schrieb:

    Ich habe übrigens nie behauptet, dass du main->try->catch als ultimative lösung beworben hättest, ich habe nur gesagt, dass es keine allgemeingültige Lösung ist, und das du mir darin bereits zugestimmt hast.

    Also moment:
    1. Du hast gesagt, dass ein globales try-catch bei der main zum Verhalten von FreeOrion führt. Ich habe bereits gesagt, dass dies nicht das Problem des globalen try-catch ist, sondern dass die Entwickler hinter FreeOrion womöglich Fehler in ihrem Exception Design haben.
    2. Später hast du meine Aussage zu einem "du brauchst nur catch in main" irgendwie umformuliert. Das habe ich aber so nie gesagt, nicht in diesem Kontext und wurde auch in diesem Thread nie so erwähnt. Da nur ich allerdings etwas von catch in der main gesagt habe, habe ich dies darauf zurückgeführt, dass du meinst, ich hätte dies so gemeint.
    3. Du hast dann gesagt: "Es ist der Beweis dafür, dass die Pauschalaussage, das try-catch in main() alle Probleme pauschal löst, falsch ist.". Welche Pauschalaussage nun, wenn du behauptest, dass du nie behauptet hättest, dass ich main-try-catch als Pauschallösung für alle Probleme beworben hätte.

    ... uh, der letzte Satz gibt einem wirklich Kopfweh 🤡

    Grüssli



  • 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 noexcept mehr als throw() , abgesehen vom Operator?

    Und warum kann die Nothrow-Exceptionsicherheit über noexcept nicht zur Kompilierzeit erzwungen werden, indem noexcept -Funktionen keine throw -Statements enthalten dürfen und nur andere noexcept -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 main ein globales try-catch machen.

    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) von throw() 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 new operator (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*.h
    

    Spezis raus aus den Definitionen:

    $ perl -p -i.bak -e '^throw\([^)]*\)\s*$//mig' T*.h
    

    Einmal astyle drüberlaufen lassen, feddich! 😃



  • Nexus schrieb:

    Was kann denn noexcept mehr als throw() , 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 noexcept nicht zur Kompilierzeit erzwungen werden, indem noexcept -Funktionen keine throw -Statements enthalten dürfen und nur andere noexcept -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 anschliessendem catch(...) erlaubt. Ist natürlich nicht gross durchdacht. Klar, man würde die Komplexität der Sprache durch einen zusätzlichen propagierenden Sicherheitsmechanismus neben const und volatile stark 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, noexcept sei stattdessen die Halbwegs-Lösung, weil ein richtiger Mechanismus doch zu viel wäre, aber man vom bösen throw() 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 aus terminate() hatte.
    Es ist jedoch so, dass es einen durchaus bemerkenswerten Nutzen der Ausnahmespezifikation gibt, der bei einer nicht-gelisteten Ausnahme kein terminate() 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() und atexit() , 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.com

    Steve 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. 😉


Anmelden zum Antworten