C++ Exceptions und Relevanz



  • Andromeda schrieb:

    Und zwar immer so, dass sich ein Thread selbst beendet. Von außerhalb sollte er nur das Signal bekommen, dass er (bald) sterben soll. Einen Thread zu killen ist immer problematisch, da man nie weiß was er gerade macht.

    Ich mein ja auch nicht von killen, die exception wird schon von dem thread selbst geschmissen.

    Shade Of Mine schrieb:

    Ah, OK - für Thread Beenden ist das eine häßliche Möglichkeit die man gerne aus Bequemlichkeit verwendet. Da kann es OK sein. Es spart Code und das ist es oft Wert.

    Ja, aber das Problem warum man das da macht ist ja eigentlich unabhängig von dem Thread-Zeug: Man will halt nicht manuell aus etlichen Unterfunktionen rausgehen sondern direkt, das meinte ich.

    Deswegen sehe ich exceptions halt auch nicht nur als Fehler-Behandlungs-Mechanismen - ein throw ist ja auch eigentlich überhaupt nicht an eine exception gebunden. Man kann schließlich auch beliebige Objekte werfen (die dann auch nicht mehr das Wort "Ausnahme" mit sich tragen).

    Das gleiche Problem kann ich ja auch ohne threads haben:

    struct tier
    {
        struct fressfeind_alarm {};
    
        void such_nach_essbarem()
        {
            if (fressfeind_in_sicht())
                throw fressfeind_alarm(); // lass alles stehn und liegen
        }
    
        void suche_essen()
        {
            geh_in_gefaehrliches_gebiet();
            such_nach_essbarem();
            geh_in_sicheres_gebiet();
            schlaf_ein_paar_stunden();
        }
    
        void mach_was() 
        {
            while (true) {
                try { 
                    suche_essen() 
                } 
                catch (fressfeind_alarm &) { 
                    lauf_weg(); 
                }
            }
        }
    };
    

    Hier will man ja auch nicht unbedingt durch alle Funktionen zurückschleusen dass alles abgebrochen werden soll. Im Gegenteil, mMn passt eine exception (bzw. ein throw) hier perfekt zu der Modellierung des Tagesablaufs.



  • happystudent schrieb:

    Ja, aber das Problem warum man das da macht ist ja eigentlich unabhängig von dem Thread-Zeug: Man will halt nicht manuell aus etlichen Unterfunktionen rausgehen sondern direkt, das meinte ich.

    Das sollte aber nicht vorkommen. Thread beenden ist so eine Ausnahme Sache da man einen Thread manchmal eben killen will. Aber Prinzipiell ist flowcontrol mit Exceptions richtig mies.

    Nicht nur weil es lahm hoch 3 ist sondern auch weil es schwer nachverfolgbar ist und korrektheit nur schwer nachweisbar ist.

    Im Gegenteil, mMn passt eine exception (bzw. ein throw) hier perfekt zu der Modellierung des Tagesablaufs.

    Nein, hier ist das Design unpassend.

    Hier brauchst du ein Signal/Event basierstes System. Denn du willst auf Events (Feind gesichtet, Futter gesichtet, Wetter hat sich geändert, Es ist Nacht geworden, Magen voll, etc.) reagieren und da sind Exception einfach eine schlechte Lösung.

    Deshalb bin ich auch gegen das Thread-Beispiel. Es ist Excpetion-Missbrauch. Manchmal muss man Sachen missbrauchen um unmengen an Code zu sparen, aber Missbrauch bleibt Missbrauch und bitte bloß nicht in andere Situationen übertragen.



  • happystudent schrieb:

    Ja, aber das Problem warum man das da macht ist ja eigentlich unabhängig von dem Thread-Zeug: Man will halt nicht manuell aus etlichen Unterfunktionen rausgehen sondern direkt, das meinte ich.

    Wie setjmp/longjmp, die gibt es schon seit den C-Tagen.
    C++-Exceptions eröffnen dem Programmierer einen alternativen Ausführungspfad. Sie sind im Grunde eine Untergruppe der Coroutines. Nur dass es keinen Rückweg ins bisherige Level (wie finally in Java) gibt.



  • Andromeda schrieb:

    Wie setjmp/longjmp, die gibt es schon seit den C-Tagen.

    Meiner Meinung nach ist setjmp/longjmp eins der schrecklichsten C-Features überhaupt. Das ist quasi der größere, noch hässlichere, Bruder von goto. Besonders toll ist wenn man setjmp/longjmp aus einem C++ Projekt heraus nutzen muss (libpng jemand?). Man muss quasi alle Objekte die einen eigenen Destruktur definieren weit von dem Ganzen fern halten weil diese sonst im Falle eines longjmp nicht richtig zerstört werden. RAII kann man mal total vergessen.



  • sebi707 schrieb:

    Andromeda schrieb:

    Wie setjmp/longjmp, die gibt es schon seit den C-Tagen.

    Meiner Meinung nach ist setjmp/longjmp eins der schrecklichsten C-Features überhaupt. Das ist quasi der größere, noch hässlichere, Bruder von goto.

    So ist es. 😃

    C ist für echte Männer.
    C++ ist für Hausfrauen.

    Ernsthaft: wer beides beherrscht, ist gut aufgestellt. Wer aber nur auf C++ setzt und dabei C verteufelt, hat den geraden Weg in die Verdammnis eingeschlagen.


  • Mod

    Ernsthaft: wer beides beherrscht, ist gut aufgestellt. Wer aber nur auf C++ setzt und dabei C verteufelt, hat den geraden Weg in die Verdammnis eingeschlagen.

    Quatsch.



  • Arcoth schrieb:

    Ernsthaft: wer beides beherrscht, ist gut aufgestellt. Wer aber nur auf C++ setzt und dabei C verteufelt, hat den geraden Weg in die Verdammnis eingeschlagen.

    Quatsch.

    QFT!!!


  • Mod

    A language that doesn't affect the way you think about programming is not worth knowing.

    Quatsch.



  • Arcoth schrieb:

    Ernsthaft: wer beides beherrscht, ist gut aufgestellt. Wer aber nur auf C++ setzt und dabei C verteufelt, hat den geraden Weg in die Verdammnis eingeschlagen.

    Quatsch.

    Das liegt vielleicht daran, dass viele C++-Apologeten gehaupten, C++ wäre ein besseres C.



  • Shade Of Mine schrieb:

    Nein, hier ist das Design unpassend.

    Hier brauchst du ein Signal/Event basierstes System. Denn du willst auf Events (Feind gesichtet, Futter gesichtet, Wetter hat sich geändert, Es ist Nacht geworden, Magen voll, etc.) reagieren und da sind Exception einfach eine schlechte Lösung.

    Ja, das Beispiel war auch nicht ganz optimal.

    Wenn man jetzt das halt nur in 1-2 Situationen hat, dann find ichs halt Overkill extra dafür ein ganzes Event-System in den Code zu integrieren, aber ist wohl Geschmacksache.



  • Shade Of Mine schrieb:

    Ah, OK - für Thread Beenden ist das eine häßliche Möglichkeit die man gerne aus Bequemlichkeit verwendet. Da kann es OK sein. Es spart Code und das ist es oft Wert.

    Ich weiss auch nicht was daran so besonders hässlich sein soll.

    Ich weiss nur was daran (in C++) etwas problematisch ist - nämlich dass man so eine "abort exception", ganz egal welchen Typ sie hat, einfach mit catch (...) fangen und ignorieren kann.
    Was ich aber eher als Problem von C++ sehe - in C# gibt es dieses Problem z.B. nicht.



  • sebi707 schrieb:

    Andromeda schrieb:

    Wie setjmp/longjmp, die gibt es schon seit den C-Tagen.

    Meiner Meinung nach ist setjmp/longjmp eins der schrecklichsten C-Features überhaupt... (libpng jemand?).

    Libpng nicht, aber IJG-jpeg, bei der es zwischen meinen verwendeten Bibliotheken einen Versionskonflikt gab (Qt hatte seine eigene Version mitgebracht und einkompiliert). Fehler bei Initialisierung wurde nicht bedacht (ohne setjmp), dann der longjmp ins Nirvana zurück. Schon lustig zu debuggen wenn der irgendwo in den tiefen des Programms in einem der knapp 40 Threads einen Nullpointer dereferenziert und ihm dabei der Stack-Pointer fehlt... kein Call Stack... Debugger sagt: WTF? Keine Ahnung wo das grad passiert ist. Viel Spass beim Raten 😃

    Finnegan



  • Arcoth schrieb:

    A language that doesn't affect the way you think about programming is not worth knowing.

    Quatsch.

    Und was ist daran Quatsch? Mal davon abgesehen dass "worth" prinzipiell etwas Subjektives ist.

    Weißt du eigentlich was QFT bedeutet?

    @hustbaer:

    Weil man höllisch aufpassen muss. Ein zu aggresives catch irgendwo killt dir den ganzen Mechanismus und ist quasi nicht auffindbar.

    Prinzipiell ist es ein goto und das hat eben gewisse Nachteile.


  • Mod

    Shade Of Mine schrieb:

    Und was ist daran Quatsch?

    Sprachen sind Werkzeuge. Dein Zitat sagt sich schön, aber ich kann nicht erkennen, warum die genannte Bedingung hinreichend sein soll.

    Weißt du eigentlich was QFT bedeutet?

    Ja



  • Shade Of Mine schrieb:

    Weil man höllisch aufpassen muss. Ein zu aggresives catch irgendwo killt dir den ganzen Mechanismus und ist quasi nicht auffindbar.

    Das geht aber nur mit einem catch(...) (der Typ des geworfenen Objekts ist schließlich weggekapselt und nicht sichtbar). Und wenn man nach einem catch(...) nicht re-throwt ist man ja wohl selbst schuld bzw. hat eh ganz andere Probleme.

    Shade Of Mine schrieb:

    Prinzipiell ist es ein goto und das hat eben gewisse Nachteile.

    Ich finde es ist eigentlich eher ein allgemeineres return , wobei das geworfene Objekt der Rückgabewert ist.



  • Weil es gerade zum Thema passt, ein interessanter Vortrag von Andrei Alexandrescu zum Thema Fehlerbehandlung in C++, in dem er das Expected<T> -Idiom vorstellt (wer's noch nicht kennt):

    https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C

    Ich habe Expected<T> zwar noch nicht verwendet, auf den ersten Blick finde ich es jedoch sehr anprechend, da es offenbar nahezu ohne Overhead die Vorteile von Fehlerbehandlung via Rückgabewert und Exceptions in sich vereint.

    Gruss,
    Finnegan





  • expected<useless> schrieb:

    Wers noch nicht kennt: http://en.cppreference.com/w/cpp/experimental/optional

    Wenn du den Vortrag tatsächlich gesehen und verstanden hättest, würdest du wissen, dass er dort auch auf boost::optional eingeht (was de facto dasselbe ist) und wüsstest, was der Unterschied zu Expected<T> ist.

    Einfach ausgedrückt: wenn du irgendwo in deinem Code, fernab der Funktion, die den Wert gesetzt hat einen optional<T> in die Hand gedrückt bekommst und dieser keinen "value" hat, obwohl er einen haben müsste, woher weisst du dann was schiefgelaufen ist?

    Und wenn du ein Kopzept schon durch deine Namenswahl implizit als nutzlos bezeichnest, dann kann ich das nur dann akzeptieren, wenn du das auch zumindest ansatzweise begründest (lasse mich immer gerne überzeugen wenn es einleuchtet). Behauptungen aufstellen und einen sinnlosen Link posten ist da leider etwas zu wenig.

    Finnegan



  • happystudent schrieb:

    Shade Of Mine schrieb:

    Weil man höllisch aufpassen muss. Ein zu aggresives catch irgendwo killt dir den ganzen Mechanismus und ist quasi nicht auffindbar.

    Das geht aber nur mit einem catch(...) (der Typ des geworfenen Objekts ist schließlich weggekapselt und nicht sichtbar). Und wenn man nach einem catch(...) nicht re-throwt ist man ja wohl selbst schuld bzw. hat eh ganz andere Probleme.

    Achnene, da hat Shade schon Recht.
    catch (...) ohne re-throw ist das was man manchmal einfach braucht.
    Dummerweise.
    Wenn die Welt schön rosa wäre, und alle "normalen" Exception-Klassen von std::exception abgeleitet wären, dann wäre es 'was anderes. Ist aber leider nicht so. Es gibt immer noch tonnenweise Libraries die eigene Exception-Klassen verwenden die nicht von std::exception abgeleitet sind.
    Weil die Library entwickelt wurde als C++ noch nicht ordentlich standardisiert war, weil die Library von jemandem entwickelt wurde der sich nicht im Klaren darüber war was er damit anrichtet, weil von solchen Libraries "abgeschrieben" wurde ...

    Und es gibt dummerweise auch Stellen wo man ein "egal was schiefgegangen ist, hier stellen wir jetzt fest dass wir nicht machen konnten was wir machen wollten, und machen dann mit dem restlichen Programm weiter" braucht.
    An diesen Stellen dann jeweils 5+ catch Blöcke zu schreiben ist einfach nicht praktikabel. Und im Endeffekt hat man dann eine Exception-Klasse übersehen, und ist erst wieder angeschmiert.

    BTW: Den Typ des geworfenen Objekts kann man sich mittels re-throw in einer geeigneten Hilfsfunktion, die dann die dutzenden verschiedenen catch-Blöcke enthält, die man sonst an 1000 Stellen schreiben müsste, besorgen.
    Ich hab' da z.B. eine GetErrorMessageFromCurrentException() und eine LogCurrentException() Hilfsfunktion.
    Und eine Guard-Klasse die sich um so gottlose Drecksklassen wie CException (MFC) kümmert -- damit am Ende des catch-Handlers dann auch e->Delete() aufgerufen wird.
    Exception beliebigen Typs fangen + loggen + ggf. entsorgen ( CException ) beschränkt sich damit auf

    //...
    catch (...)
    {
        CleanupExceptionGuard guard;
        LogCurrentException();
    }
    

    ps: Und catch (...) mit re-throw ist ja nun wirklich bäh. Dafür baut man sich passende Hilfsklassen. Im Falle des Falles, also wenn es in einem Programm wichtig ist, kann man dann nämlich Code-Teile ohne äusseres catch laufen lassen, und die darin auftretenden Exceptions die niemand fangen mag von einem Crash-Handler verarbeiten. Der kann dann einen Dump schreiben wo das Programm genau in dem Zustand ist wo die Exception geworfen wurde. Nicht 10 Levels tiefer im Callstack wo ein blödes catch (...) mit re-throw steht, und man schon keinen Anhaltspunkt mehr hat wo die Exception überhaupt entstanden ist.
    In Anwendungscode mag das jeder halten wie er es für richtig hält. In Libraries hat ein catch (...) mit re-throw meiner Meinung nach aber genau nichts verloren.



  • hustbaer schrieb:

    BTW: Den Typ des geworfenen Objekts kann man sich mittels re-throw in einer geeigneten Hilfsfunktion, die dann die dutzenden verschiedenen catch-Blöcke enthält, die man sonst an 1000 Stellen schreiben müsste, besorgen.

    In der Präsentation von Andrei Alexandrescu (Folie 26) gibt es ein lustiges Konstrukt:

    template<class E>
    bool hasException() const {
      try {
        if(!gotHam) std::rethrow_exception(spam);
      } catch(const E& object) {
        return true;
      } catch (...) {
      }
      return false;
    }
    

    Darauf wäre ich wohl nicht so schnell gekommen...


Anmelden zum Antworten