assert() vs std::invalid_argument



  • Nexus schrieb:

    Ethon schrieb:

    Was ich aber ganz nett finde ist ein Makro zu schreiben, dass sich je nach Flags wie Assert oder Exceptionwurf verhält.

    Und im Usercode steht dann abhängig von diesem Makro ein catch , oder wie? Wäre es nicht klüger, für Python direkt ein spezifisches Assert-Makro zu schreiben als eines zu verwenden, das sich je nach Kontext anders verhält?

    Naja, bei mir fliegt die Exception statt dem Assert nur wenn "BUILD_PYTHON_MODULE" definiert ist, das passiert nicht ausversehen. 😉
    Im C++-Code gehe ich nicht davon aus, dass das jemand abfängt.



  • Ich habe früher assert benutzt, bis ich irgendwann raus gefunden hab, dass das total unkonfortabel ist. assert bringt dir nur etwas, wenn der Debugger an ist. Eine schöne Exception kann dir direkt Fehlermeldung + Position im Code mit raus geben.



  • otze schrieb:

    Position im Code

    Wie das? Das wär ja mal genial, hab ich aber noch nicht endeckt.



  • Hacker schrieb:

    otze schrieb:

    Position im Code

    Wie das? Das wär ja mal genial, hab ich aber noch nicht endeckt.

    Macros.



  • struct assertion_failure : std::logic_error
    {
        assertion_failure(std::string const& what)
            : logic_error(what)
        {}
    };
    
    #define TO_STRING_I(x) #x
    #define TO_STRING(x) TO_STRING_I(x)
    #define ASSERT(cond) if(!(cond)) throw assertion_failure(__FILE__ "(" TO_STRING(__LINE__) "): ASSERT(" #cond ")");
    


  • otze schrieb:

    Hacker schrieb:

    otze schrieb:

    Position im Code

    Wie das? Das wär ja mal genial, hab ich aber noch nicht endeckt.

    Macros.

    Ich hab manchmal sowas gemacht (nicht heulen):

    void check_pos_throw(float x, const char* n_x, float y, const char* n_y)
    {
        if(x < 0
        || y < 0
        || x > SCREEN_W
        || y > SCREEN_H)
            throw std::out_of_range(string("\ncheck_pos_throw: invalid arguments for position: X = ") + itostr(x) + string(", name of X = ") + string(n_x) + string(" || Y = ") + itostr(y) + string(", name of Y = ") + n_y);
    }
    #define check_pos_throw(x,y) check_pos_throw(x, #x, y, #y)
    


  • otze schrieb:

    Ich habe früher assert benutzt, bis ich irgendwann raus gefunden hab, dass das total unkonfortabel ist. assert bringt dir nur etwas, wenn der Debugger an ist. Eine schöne Exception kann dir direkt Fehlermeldung + Position im Code mit raus geben.

    Versteh ich nicht. Asserts sind doch eigentlich nur im Debugbuild aktiv. Machst du die Exceptions auch nur im Debugbuild rein? Und bei Windowsanwendungen öffnet sich beim Assert auch immer eine Messagebox und ich kann den VS-Debugger attachen.



  • Zwei Sachen versteh ich an PI's Idee nicht.

    #define TO_STRING_I(x) #x
    #define TO_STRING(x) TO_STRING_I(x)
    

    Wieso nicht gleich

    #define TO_STRING(x) #x
    

    Und wozu brauchst du die Struktur assertion_failure , wenn sie sich sowieso wie std::logic_error verhält?
    Oder findest du den Namen besser?

    Edit: Wäre das nicht dasselbe?

    #define ASSERT(cond) if(!(cond)) \
                             throw std::logic_error(__FILE__ "("# __LINE__ "): ASSERT(" #cond ")")
    


  • Hacker schrieb:

    Zwei Sachen versteh ich an PI's Idee nicht.

    #define TO_STRING_I(x) #x
    #define TO_STRING(x) TO_STRING_I(x)
    

    Wieso nicht gleich

    #define TO_STRING(x) #x
    

    Probiers aus. 😉

    Hacker schrieb:

    Und wozu brauchst du die Struktur assertion_failure , wenn sie sich sowieso wie std::logic_error verhält?

    Weil std::logic_error irgendwas ist und keine Assertion-Failure.



  • 314159265358979 schrieb:

    Hacker schrieb:

    Zwei Sachen versteh ich an PI's Idee nicht.

    #define TO_STRING_I(x) #x
    #define TO_STRING(x) TO_STRING_I(x)
    

    Wieso nicht gleich

    #define TO_STRING(x) #x
    

    Probiers aus. 😉

    Hab ich. 😕



  • Dann wirst du doch den Unterschied sehen, oder? 🙄



  • 314159265358979 schrieb:

    Dann wirst du doch den Unterschied sehen, oder? 🙄

    N-Ö?

    #define TO_STRING_I(x) #x
    #define TO_STRING(x) TO_STRING_I(x)
    
    int main()
    {
        std::cout << string() + TO_STRING_I(5) << '\n'
                  << string() +  TO_STRING(6) << '\n';
    }
    


  • Dann probiers mal mit __LINE__. 😡



  • otze schrieb:

    Ich habe früher assert benutzt, bis ich irgendwann raus gefunden hab, dass das total unkonfortabel ist. assert bringt dir nur etwas, wenn der Debugger an ist. Eine schöne Exception kann dir direkt Fehlermeldung + Position im Code mit raus geben.

    Assertions benutzt man ja auch nur um Programmierfehler abzufangen.

    assert(ptr != nullptr)
    

    Gibt mir die Information darüber dass hier ein Pointer null ist und auch die Position im Code. Die Exception bringt hier keine Vorteile, ohne Macros aber Runtime-Overhead.



  • Ethon__ schrieb:

    Gibt mir die Information darüber dass hier ein Pointer null ist und auch die Position im Code. Die Exception bringt hier keine Vorteile, ohne Macros aber Runtime-Overhead.

    Natürlich sollte sich ein assert Ersatz der auf Expcetions basiert, sich genauso verhalten wie assert: im Release kein Overhead. Warum bist du jetzt schon der Dritte mit dieser Anmerkung?

    So siehts aus:

    #define EXCEPTION(message) Exception(message, __FILE__, __LINE__)
    
    #ifndef NDEBUG
    #define SIZE_CHECK(cond) do { if (!(cond)) throw EXCEPTION("size mismatch:"#cond); } while (false)
    #else
    #define SIZE_CHECK(cond) do { (void)sizeof(cond); } while (false)
    #endif
    
    //Benutzung:
    SIZE_CHECK(vector1.size()==vector2.size());
    


  • IMO sollte man das Semikolon ins Makro reinziehen, da Makro selbst weiß, dass eines benötigt wird. Für den Benutzer des Makros ist das oft nicht sofort ersichtlich. Meine Faustregel: Alle Makros so gestalten, dass keine abschließenden Semikola benötigt werden. Wozu du hier do-while verwendest, verstehe ich nicht.



  • 314159265358979 schrieb:

    Wozu du hier do-while verwendest, verstehe ich nicht.

    Um ein Semikolon hinter den Aufruf setzen zu können:

    #define SET_TO_NULL(x) { x = 0; cout << "SET_TO_NULL" << endl; }
    
    #define SET_TO_NULL2(x) do { x = 0; cout << "SET_TO_NULL2" << endl; } while(false)
    
    int main()
    {
    	int x = 4;
    	if(x != 0)
    		SET_TO_NULL(x);
    	else                                              // Fehler: else ohne if
    		cout << "Nichts zu tun hier" << endl;
    
    	x = 4;
    	if(x != 0)
    		SET_TO_NULL2(x);
    	else
    		cout << "Nichts zu tun hier" << endl;
    }
    

    Ich finde ein Semikolon hinterm Aufruf schöner, weil es so konform zu allen anderen Ausdrücken ist.



  • 😃

    Naja, ich finde es doof, dass man bei manchen Makros ein setzen muss und bei anderen nicht. Daher mach ichs so, dass man nie eines setzen muss.



  • Und ich finde es doof, wenn Makros sich anders verhalten als Funktionsaufrufe.
    Da ist die do-while Technik ideal, denn die funktioniert überall wie ein Funktionsaufruf, auch in {}-losen Schleifen oder sogar verschachtelten if's.

    z.T. ASSERT(): man kann ja auch nen Dump schreiben und den Prozess dann abbrechen. Dann hat man nicht nur Sourcefile/Zeilennummer, sondern gleich die Callstacks von allen Threads, und je nach Einstellungen sogar den gesamten Speicherinhalt.



  • hustbaer schrieb:

    z.T. ASSERT(): man kann ja auch nen Dump schreiben und den Prozess dann abbrechen. Dann hat man nicht nur Sourcefile/Zeilennummer, sondern gleich die Callstacks von allen Threads, und je nach Einstellungen sogar den gesamten Speicherinhalt.

    Wie würde das mit dem Callstack plattform/compilerunabhängig gehen? Interesse an erweitertern Fehlermeldungen habe ich immer.


Anmelden zum Antworten