I love Lambdas



  • schöner Hackaround, oder Mist? (oder Overkill?)

    class Exception : public std::exception
    {
    public:
        Exception(std::string message, unsigned int code, int line, std::vector <std::string> const& additional_parms)
            : message_ 
            {
                [&]() /*noexcept*/ -> std::string
                {
                    // modify message (replace stuff with additional parameters)
                    return message_;
                }()
            }
            , code_{code}
            , line_{line}
            , fileName_{std::move(fileName)}
        {
            // cannot modify / assign message_ here.
        }
    
        // what, message, code...
    
    private:
        const std::string message_; // CONST, weil... warum nicht?
    }
    


  • 1. Exceptions sollten keinen dynamischen Speicher besitzen (std::string), weil das bad_alloc o.Ä. provozieren kann. Also nur const char*+int.

    2. Die {}-Initialisierung um alles in der Welt vermeiden. Hier ist sie sinnlos.

    3. Bei Konstruktoren gehe ich normalerweise immer den Weg über delegierte Konstruktoren

    class NotAnExceptionClass : public std::exception
    {
    public:
        // Parameter genau das, was in Membern abgelegt wird.
        NotAnExceptionClass(std::string message, unsigned int code, int line)
            : message_(std::move(message))
            , code_(code)
            , line_(line)
            , fileName_(std::move(fileName))
        {
        }
    
        NotAnExceptionClass(std::string message, unsigned int code, int line, std::vector <std::string> const& additional_parms)
            NotAnExceptionClass(format_msg(message, additional_params)), code, line)
            // format_msg kann auch ein Lambda sein, aber das kommt äusserst selten vor
        {}
    
        // what, message, code...
    
    private:
        const std::string message_; // CONST, weil... warum nicht?
    }
    

    4. const-Member sind böse, weil der effiziente move-Konstruktor/move-Operator dadurch unmöglich wird.

    5. Lambdas zur Initialisierung verwende ich in der Praxis nur zur Initialisierung von statischen (statisch in einer Funktion) oder globalen Variablen



  • critizizer schrieb:

    1. Exceptions sollten keinen dynamischen Speicher besitzen (std::string), weil das bad_alloc o.Ä. provozieren kann. Also nur const char*+int.

    aber ignoriere ich wissentlich. Wenn das passiert, darf mir das gerne um die Ohren fliegen.

    critizizer schrieb:

    2. Die {}-Initialisierung um alles in der Welt vermeiden. Hier ist sie sinnlos.

    hmmm, ich bin eher gerade beim Umstellen in die andere Richtung...

    critizizer schrieb:

    4. const-Member sind böse, weil der effiziente move-Konstruktor/move-Operator dadurch unmöglich wird.

    Interessant.



  • critizizer schrieb:

    1. Exceptions sollten keinen dynamischen Speicher besitzen (std::string), weil das bad_alloc o.Ä. provozieren kann. Also nur const char*+int.

    So allgemein würde ich das nicht formulieren, wenn man von runtime_error ableitet ist das kein Problem.

    critizizer schrieb:

    4. const-Member sind böse, weil der effiziente move-Konstruktor/move-Operator dadurch unmöglich wird.

    👍 Und nicht nur deshalb, auch ein assignment Operator ist dadurch nicht mehr möglich. Tatsächlich sollte man so oft es geht, und es geht eigentlich immer, auf const-member verzichten.

    Aber generell finde ich diese Exception-Klasse suboptimal designed, mal ganz unabhängig von der Lambda. Wozu haben wir denn mehrere Typen und ein virtual what()? Um hinterher wieder eine Mutter-Aller-Exceptions-Klasse zu bauen? Nein danke. Einfach einen runtime_error oder ein kleines von runtime_error abgeleitetes Objekt werfen, bitte nichts mit unspezifizierten Extra-Parametern oder Code-Lines. Über error-codes kann man streiten, aber auch dann eher im Stil von http://en.cppreference.com/w/cpp/header/system_error



  • Ok, das nimmt eine interessante Richtung gen der Exception class, deshalb mehr Struktur:

    class Message : public std::exception // derived for compatibility
    // (ist eigentlich nicht nötig, nur für what())
    {
        // ... eingies vom ersten post
    }
    
    class Warning : public Message
    {
        // ...
    }
    
    class Error : public Message
    {
        // ...
    }
    
    #define REGISTER_ERROR(NAME)
    // leite hier NAME von Error ab und überlade diverse methoden in Makro
    // (zum Beispiel what)
    // das ist dann zentralisiert mit enum error codes und den dazugehörigen messages
    
    #define REGISTER_WARNING(NAME)
    // selbes. siehe oben
    

    später:

    // hier ein paar makros, die einfach zeigen sollen, was ich dann mit den Klassen mache:
    #define ERROR(TYPE, /*more*/) // fügt Fehler einer Liste hinzu, kein Abbruch
    #define WARN(TYPE, /*more*/)  // fügt Warnung einer Liste hinzu
    #define FATAL(TYPE, /*more*/) // throw TYPE{/*more*/}
    

    EDIT: warum kopiert oder moved man exceptions rum? Das klingt nach einer dummen idee! (moved durchgestrichen, weil das kann ich mir noch vorstellen)

    EDIT2: Das replacing ist um Zusatzinformationen in die Fehler zu bekommen, da die Fehlermeldung nicht im fehlerproduzierenden Code selbst zusammengesetzt werden, sondern, woanders liegen. Das Ganze ist dann im std::snprintf style.



  • cooky451 schrieb:

    critizizer schrieb:

    1. Exceptions sollten keinen dynamischen Speicher besitzen (std::string), weil das bad_alloc o.Ä. provozieren kann. Also nur const char*+int.

    So allgemein würde ich das nicht formulieren, wenn man von runtime_error ableitet ist das kein Problem.

    Ich würd schon sagen dass es scheisse ist, wenn während des Exception-Handlings eine std::bad_alloc Exception fliegt. So double-faultet man sich schnell ins Abseits.

    Daher sollte man in seinen Exception-Klassen auch hübsch shared_ptr<string const> statt string verwenden. So sind die dann nämlich noexcept-kopierbar.

    @Tim06TR
    Die "schöne" Variante wäre mMn. eher die Werte beim Erzeugen der Exception einfach nur alle unverändert in die Member zu packen, und die Message dynamisch zu formatieren wenn what() aufgerufen wird.
    Damit der String nach dem Aufruf von what() bestehen bleibt, verpasst man der Exception Klasse einfach nen zusätzlichen shared_ptr<string const> , der erstmal Leer ist, und erst beim ersten Aufruf von what() befüllt wird.



  • hustbaer schrieb:

    Daher sollte man in seinen Exception-Klassen auch hübsch shared_ptr<string const> statt string verwenden. So sind die dann nämlich noexcept-kopierbar.

    Hmmm, ja, nette Idee. 👍


  • Mod

    und es geht eigentlich immer, auf const-member verzichten.

    Eigentlich?

    Übrigens, Lambdas kann man kürzer schreiben

    [&]
    {
        // modify message (replace stuff with additional parameters)
        return message_;
    }
    

Anmelden zum Antworten