Wann noexcept nutzen?



  • wannnoexcept schrieb:

    ich habe in meiner Applikation momentan mehrere Model-Klassen deren Methoden meistens keine Exception werfen. Ist es sinnvoll diese mit noexcept zu deklarieren?

    "no" heißt nicht "selten", sondern "keine".

    Das sollte man nicht einfach überall hinschreiben, wo aktuell keine Exceptions fliegen. Das kann sich schließlich mal ändern.

    Hauptsächlich ist noexcept für Konstruktoren, Zuweisungsoperatoren und den Destruktor gedacht, um ausnahmesicheren, generischen Code effizienter zu machen. Zum Beispiel kann std::vector beim Vergrößern unnötige Kopien vermeiden, wenn die Elemente noexcept -movable sind.

    Man kann noexcept zur Mikrooptimierung benutzen, weil der Compiler unter Umständen besseren Code generieren kann, wenn ein Aufruf garantiert nicht wirft.

    (Sorry, den muss ich einfach hinschreiben: "Ohne Ausnahme: noexcept lässt keine Exceptions zu".)

    Ruvi schrieb:

    Soweit ich das verstanden habe kann der Compiler dann Optimierungen vornehmen die er ansonsten nicht vornehmen könnte ABER sollte dort doch eine Exception rauskommen dann landest, wenn ich mich richtig erinnere im UB-Land.

    Nö, das endet mit std::terminate . Der Stack darf vom Compiler abgeräumt werden, muss aber nicht.



  • TyRoXx schrieb:

    Nö, das endet mit std::terminate . Der Stack darf vom Compiler abgeräumt werden, muss aber nicht.

    Ich kann es leider nicht testen VS hat noexcept noch nicht ordentlich implementiert, also wird std::terminate aufgerufen sobald eine Exception von einer noexcept Funktion geworfen wird?
    Oder meinst Du, dass die Exception die dort geworfen wird wahrscheinlich nicht gefangen wird und durch die Exception selbst std::terminate aufgerufen wird?



  • Ruvi schrieb:

    Ich kann es leider nicht testen VS hat noexcept noch nicht ordentlich implementiert, also wird std::terminate aufgerufen sobald eine Exception von einer noexcept Funktion geworfen wird?
    Oder meinst Du, dass die Exception die dort geworfen wird wahrscheinlich nicht gefangen wird und durch die Exception selbst std::terminate aufgerufen wird?

    Normalerweise geht das Programm bei einer exception dazu ueber, den ganzen Stack abzuraeumen und alle Destruktoren aufzurufen, bis man beim entsprechenden catch ist. Bei noexcept kann einfach sofort beendet werden und man spart sich dadurch die ganze Fehlerbehandlung und macht ggf. den Code schneller.



  • Hö?

    D.h. bei noexcept werden Destruktoren nicht mehr aufgerufen?
    Das scheint mir jetzt auf den ersten Blick etwas, naja, komisch.



  • Finde ich gar nicht komisch. Bestenfalls kann der Stack des Threads abgeräumt werden, in dem die Exception auftritt. Wenn du das wirklich brauchst, kann du immer noch mit catch das Aufräumen erzwingen, wo du es haben möchtest.

    Das terminate bei noexcept ist überhaupt nicht dazu gedacht das Programm "normal" zu beenden. Es soll nur ein definiertes Verhalten schaffen, wo keine sinnvolle Fehlerbehandlung mehr möglich ist.

    Es ist ein wenig schade, dass man noexcept nicht strenger gemacht hat: Eine noexcept -Funktion sollte nur aus noexcept -Statements bestehen dürfen. Der nächste logische Schritt wäre dann noexcept(auto) , das bei Templates und inline -Funktionen angewendet werden könnte.


  • Mod

    Eine noexcept -Funktion sollte nur aus noexcept -Statements bestehen dürfen.

    I.e. folgender Code

    void foo() noexcept {
        try {somethingThatThrows();}
        catch(...) {std::cerr << "eww.";}
    }
    

    Ist verboten? Gut, okay. Wir führen eine Regelung ein die es uns erlaubt beliebigen Code in einem try -Block unterzubringen - aber nur wenn dieser von einem catch(...) { /*...*/ } gefolgt wird, welcher selbst tatsächlich nur aus noexcept Statements besteht (wobei die obige Regel rekursiv anwendbar ist). Was ist mit

    void foo() noexcept {
        try {somethingThatThrowsInt();}
        catch(int) {std::cerr << "eww.";}
    }
    

    ? Ups, catch(...) fehlt. Schnell ergänzen:

    void foo() noexcept try {
        try {somethingThatThrowsInt();}
        catch(int) {std::cerr << "eww.";}
    }
    catch(...) {std::terminate();}
    

    Aber Moment, das letzte catch(...) in dieser Form können wir doch automatisch anhängen lassen? ... 💡



  • Ich finde es nicht gut, wenn der Compiler Code generiert, der das Programm abschießt, wenn er das Problem auch schon beim Übersetzen hätte erkennen können. Das terminate -Verhalten hätte man sehr gut in der Standardbibliothek umsetzen können. Da wäre keine Compiler-Magie notwendig gewesen.

    namespace std
    {
    	template <class F, class ...Args>
    	auto terminate_on_exception(F &&f, Args &&...args) noexcept
    	{
    		try
    		{
    			return std::forward<F>(f)(std::forward<Args>(args)...);
    		}
    		catch (...)
    		{
    			std::terminate();
    		}
    	}
    }
    
    void foo() noexcept
    {
    	std::terminate_on_exception([]
    	{
    		try {somethingThatThrowsInt();}
    		catch(int) {std::cerr << "eww.";}
    	});
    }
    

  • Mod

    Und new willste auch jedes mal kapseln?

    PS: Da solltest du eigentlich eher invoke anwenden.



  • Ruvi schrieb:

    Wenn aus diesen Methoden garantiert keine Exceptions geworfen werden können (mit anderen Worten aus dem gesamten Call-Stack der jeweiligen Funktion) sehe ich persönlich keinen Grund warum Du nicht noexcept ranschreiben solltest.

    Doch, es gibt einen Grund.
    noexcept ist zumindest fraglich bei Funktionen, die Parameter "by value" nehmen, deren Copy-Konstruktor werfen kann (bzw. allgemein: die einen Konstruktor haben der etwas werfen könnte).

    Aus Sicht des Standard kann die Funktion zwar trotzdem noch noexcept sein, aber nur weil der Aufruf der nötigen Konstruktoren für Parameter aus Sicht des Standards beim Aufrufer passiert.
    Und da noexcept für eine solche Funktion mMn. klar das principle of least astonishment verletzt, sollte man es weglassen.
    (Was bringt es schon dass die Funktio noexcept ist, wenn man sie nicht noexcept aufrufen kann?)



  • ps: Einige Funktionen, wie z.B. auto-generierte Konstruktoren, haben ja einen automagischen noexcept("true, wenns nicht gelogen ist, und sonst halt false") specifier.
    Wäre es nicht praktisch das auch für inline-definierte Funktionen zu erlauben?
    Ala

    inline void Fun() noexcept(auto)
    {
        // Do stuff that might or might not all be noexcept(true)
    }
    

    EDIT: OK, gibt eh schon (lange) ein Proposal: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3207.htm
    Weiss jemand was der Status davon ist?
    Das darin beschriebene "Problem 1" ist mMn. keines, wenn man es auf inline definierte Funktionen einschränkt.
    Und das darin beschriebene "Problem 2" ist glaube ich auch schon gelöst, da noexcept Specs laut Standard erst nach der Overload-Resolution angeguckt werden (oder hab ich da falsche Infos/etwas falsch verstanden?).

    EDIT2: Sorry TyRoXx, hab jetzt erst gesehen dass du auch bereits noexcept(auto) angesprochen hattest.



  • Arcoth schrieb:

    Und new willste auch jedes mal kapseln?

    Du meinst make_unique und make_shared ?
    "Jedes mal"? Es geht hier nur um noexcept -Funktionen.

    Arcoth schrieb:

    PS: Da solltest du eigentlich eher invoke anwenden.

    Stimmt, aber ich bin gedanklich noch nicht im Jahr 2017 angekommen.

    hustbaer schrieb:

    noexcept ist zumindest fraglich bei Funktionen, die Parameter "by value" nehmen, deren Copy-Konstruktor werfen kann (bzw. allgemein: die einen Konstruktor haben der etwas werfen könnte).

    Ich weiß nicht, wie praktikabel das ist, aber ein Compiler könnte da warnen.



  • Ne Warning wäre mMn. sehr praktikabel 🙂
    Oder meinst du jetzt wie schwer/einfach das im Compiler umsetzbar ist (was ich auch nicht weiss, schätze aber das kann kein ganz grosses Problem sein)?


Anmelden zum Antworten