Exceptions filtern und weiterleiten



  • Um Exceptions zu filtern möchte ich folgende Konstruktion einsetzen. Ich bin mir aber nicht sicher, ab das vielleicht auf andere Weise eleganter zu realisieren ist.

    Der Sinn von Folgendem ist nur, dass keine Exception ungeprüft geworfen wird und NUR exceptions vom Typ <MyException> geworfen werden.

    enum MyExceptionID {
      MY_BAD,
      MY_FATAL,
    };
    
    class MyException: public std::exception {
    public:
      explicit MyException(MyExceptionID id) :
          std::exception("MyException"), m_id(id) {
      }
    
      MyExceptionID ExceptionID() const {
        return m_id;
      }
    
    private:
      MyExceptionID m_id;
    };
    
    void Function(const std::string& string) {
      try {
        if (string == "runtime_error") {
          throw std::runtime_error("runtime_error");  // std::runtime_error
        }
    
        if (string == "MyException") {
          throw MyException(MY_BAD);                  // MyException
        }
    
        // Do complex operations that could throw various exceptions
        // ...
        std::string temp(string);
        std::string::size_type pos = temp.rfind(",");
        temp.erase(pos);                              // std::out_of_range
        // ...
      } catch (const MyException& e) {
        // Forward the exception
        throw e;
      } catch (const std::exception& e) {
        // Do something with the exception
        // ...
        throw MyException(MY_FATAL);
      }
    }
    
    int main() {
      try {
        Function("missingcomma");
      } catch (const MyException& e) {
        std::cout << "My Exception: " << e.ExceptionID() << std::endl;
      }
    
      return 0;
    }
    


  • Wsa erscheint dir denn daran unelegant? Du könntest jetzt noch eine Funktion höherer Ordnung schreiben, die das try-catch-Zeug automatisch um deine Funktion herum schreibt.

    Aber noch ein paar Kommentare: Eine Exception wirft man durch throw; weiter, ohne Parameter. Alle weiteren Exceptions würde ich mit catch(...) fangen, weil jemand nicht von std::exception geerbt haben könnte. Das const bei den Exceptions ist auch ziemlich überflüssig, weil Exceptions by value geworfen werden.



  • Michael E. schrieb:

    Wsa erscheint dir denn daran unelegant? Du könntest jetzt noch eine Funktion höherer Ordnung schreiben, die das try-catch-Zeug automatisch um deine Funktion herum schreibt.

    Aber noch ein paar Kommentare: Eine Exception wirft man durch throw; weiter, ohne Parameter. Alle weiteren Exceptions würde ich mit catch(...) fangen, weil jemand nicht von std::exception geerbt haben könnte. Das const bei den Exceptions ist auch ziemlich überflüssig, weil Exceptions by value geworfen werden.

    Danke für den Hinweis mit const!

    Das catch(...) wollte ich eigentlich vermeiden.



  • Tomahawk schrieb:

    } catch (const MyException& e) {
        // Forward the exception
        throw e;
      }
    

    Das ist besser als

    } catch (const MyException&) {
        // Forward the exception
        throw; // <-- ohne Objekt
      }
    

    geschrieben, sonst kriegst du Slicing-Probleme, wenn jemand von deiner Exceptionklasse erbt.


  • Mod

    seldon schrieb:

    Tomahawk schrieb:

    } catch (const MyException& e) {
        // Forward the exception
        throw e;
      }
    

    Das ist besser als

    } catch (const MyException&) {
        // Forward the exception
        throw; // <-- ohne Objekt
      }
    

    geschrieben, sonst kriegst du Slicing-Probleme, wenn jemand von deiner Exceptionklasse erbt.

    Da musste ich jetzt genau hinschauen. Beim Querlesen habe ich erst mal nur

    Code

    Das ist besser als

    Code

    gesehen.



  • Michael E. schrieb:

    Das const bei den Exceptions ist auch ziemlich überflüssig, weil Exceptions by value geworfen werden.

    Wenn man etwas const machen kann, ist das nie überflüssig.



  • TyRoXx schrieb:

    Wenn man etwas const machen kann, ist das nie überflüssig.

    Schreibst du das so?

    const int verdopple(const int i)
    {
        return 2 * i;
    }
    

    Und so?

    const int verdopple(const int* const i)
    {
        return 2 * (*i);
    }
    

    Edit: double ist kein toller Funktionsname.



  • Michael E. schrieb:

    TyRoXx schrieb:

    Wenn man etwas const machen kann, ist das nie überflüssig.

    Schreibst du das so?

    const int verdopple(const int i)
    {
        return 2 * i;
    }
    

    Und so?

    const int verdopple(const int* const i)
    {
        return 2 * (*i);
    }
    

    An so einen Schwachsinn habe ich bei der Behauptung natürlich nicht gedacht. Ich würde es falsch nennen, einen Rückgabewert const zu machen, nicht "überflüssig".
    Das bei den Argumenten kann man bei der Definition von mir aus machen. Warum sollte man die anders behandeln als andere lokale Variablen?
    Man macht die Parameter aber nicht const , damit die Parameterlisten bei Deklaration und Definition gleich sind (bzw. damit man die nicht jeweils anpassen muss).
    Wenn man einfach bei beidem const schreibt, steht in der Deklaration ein Implementationsdetail. Für den Benutzer der Funktion nutzlos und eher störend.



  • TyRoXx schrieb:

    ...

    Wieso falsch? Überflüssig trifft es viel besser:

    1. Wenn du Argumente by-value übergibst, machst du die Parameter nicht const , da es redundant wäre. Du kannst das Original sowieso nicht ändern, da du ja nur eine Kopie zur Verfügung hast.

    2. Wenn du einen fundamentalen Typen by-value zurückgibst, machst du den Rückgabetypen nicht const , da es redundant wäre, da der Rückgabewert per Definition ein rvalue ist. Einem rvalue kannst du eh nichts zuweisen.



  • TyRoXx schrieb:

    Warum sollte man die anders behandeln als andere lokale Variablen?

    Du lieferst im nächsten Satz selbst ein Argument: Was meinst du eigentlich mit "damit man die nicht jeweils anpassen muss"?



  • out schrieb:

    2. Wenn du einen fundamentalen Typen by-value zurückgibst, machst du den Rückgabetypen nicht const , da es redundant wäre, da der Rückgabewert per Definition ein rvalue ist. Einem rvalue kannst du eh nichts zuweisen.

    Laut C++11 ein prvalue. 🤡



  • out schrieb:

    1. Wenn du Argumente by-value übergibst, machst du die Parameter nicht const , da es redundant wäre. Du kannst das Original sowieso nicht ändern, da du ja nur eine Kopie zur Verfügung hast.

    Wieso? Ich nutze const wo ich kann. Und damit garantiert man dem Aufrufer und sich selbst, dass der Parameter im Verlauf der Funktion immer denselben Wert hat.



  • out schrieb:

    1. Wenn du Argumente by-value übergibst, machst du die Parameter nicht const , da es redundant wäre. Du kannst das Original sowieso nicht ändern, da du ja nur eine Kopie zur Verfügung hast.

    Aber die Kopie kann geändert werden.

    out schrieb:

    2. Wenn du einen fundamentalen Typen by-value zurückgibst, machst du den Rückgabetypen nicht const , da es redundant wäre, da der Rückgabewert per Definition ein rvalue ist. Einem rvalue kannst du eh nichts zuweisen.

    Man kann ihn aber als LValue binden und dann möchte man kein const . Wegen der Einheitlichkeit und weil es keinen positiven Effekt hat, schreibt man generell nicht const bei Rückgabewerten. Es ist sozusagen falsch.

    Michael E. schrieb:

    TyRoXx schrieb:

    Warum sollte man die anders behandeln als andere lokale Variablen?

    Du lieferst im nächsten Satz selbst ein Argument: Was meinst du eigentlich mit "damit man die nicht jeweils anpassen muss"?

    Deklaration: Kein const weil das ein Implementationsdetail ist und nicht zur Signatur gehört.
    Definition: const , weil sinnvoll.
    Man müsste also ständig darauf achten, const zu ergänzen oder zu entfernen. C++ ist aber schon Tipperei genug. Es ist damit nicht unnötig, sondern unpraktisch.

    Mal was zum Anfassen:

    //so soll das für den Benutzer aussehen
    int add(int a, int b);
    
    //ist länger ohne Mehrwert
    int add(int const a, int const b); 
    
    //const verhindert versehentliches Ändern
    int add(int const a, int const b)
    {
    	some_obscure_call(a); //wird a hier geändert? Ah, kann nicht wegen const.
    
    	for (int i = 0; i < a; ++a); //Tippfehler kompiliert nicht
    
    	return a + b;
    }
    

    Sone schrieb:

    out schrieb:

    1. Wenn du Argumente by-value übergibst, machst du die Parameter nicht const , da es redundant wäre. Du kannst das Original sowieso nicht ändern, da du ja nur eine Kopie zur Verfügung hast.

    Wieso? Ich nutze const wo ich kann. Und damit garantiert man dem Aufrufer und sich selbst, dass der Parameter im Verlauf der Funktion immer denselben Wert hat.

    Dem Aufrufer ist es egal, welche Werte die Kopie annimmt.



  • Sone schrieb:

    Laut C++11 ein prvalue.

    prvalues sind rvalues. Einfach mal die... du weisst schon.



  • Kellerautomat schrieb:

    Sone schrieb:

    Laut C++11 ein prvalue.

    prvalues sind rvalues.

    C++11 macht einen Unterschied zwischen prvalue und rvalue. Sonst würde man nicht zwei unterschiedliche Ausdrucksklassen daraus machen.

    Dass es hier irrelevant ist, dürfte mehr als offensichtlich sein 🤡



  • TyRoXx schrieb:

    Dem Aufrufer ist es egal, welche Werte die Kopie annimmt.

    Naja, ich dachte, wenn er nach Bugs in der Implementierung sucht... 😃



  • Mir gefaellt const in der Signatur deshalb nicht, weil es implementierungsdetails nach aussen traegt.

    Bestes Beispiel ist hier wohl strcpy:

    char* strcpy(char* trg, char const* src);
    

    das muesste ja wenn ich ueberall const schreibe wo ich const schreiben kann so lauten:

    char* const strcpy(char* const trg, char const* const src);
    

    Das const beim Returntyp ignorieren wir mal, weil das ein anderes Thema ist. Betrachten wir nun aber die Standard Implementierung von strcpy:

    while(*trg++=*src++)
      ;
    

    geht aber jetzt nicht mehr. Doof sowas. Wir muessen jetzt ploetzlich unnoetig kopieren.

    Es gibt einige Gruende warum ich den Parameter aendern will. Und in der Signatur erkennt man nun ob ich das tue. Sowas mag ich nicht.

    Prinzipiell ist const ueberall zu schreiben toll, wenn man dann aber inkonsistent werden muss, ists doof. Und ueberall Kopien ziehen will ich nicht.



  • Wieso ist es so ein "Problem", wenn du verrätst dass du innen den Zeiger veränderst? Klar, du willst Implementierungsdetails geheim halten. Aber das ist doch wirklich etwas übertrieben...



  • Sone schrieb:

    Wieso? Ich nutze const wo ich kann. Und damit garantiert man dem Aufrufer und sich selbst, dass der Parameter im Verlauf der Funktion immer denselben Wert hat.

    Du hast es nicht verstanden: Wenn du by-value machst, dann weiß der Aufrufer doch schon längst, dass sein Argument auch nach der Funktion noch denselben Wert hat, da das Argument ja kopiert wird, verstehst du?
    Wenn du als Entwickler sichergehen willst, dass der Parameter stets denselben Wert hat, machst du den Parameter trotzdem nicht const:

    1. Weil es den Aufrufer verwirrt. Er wundert sich, dass bei by-value der Paramter const ist und will wissen, was es damit auf sich hat.

    2. Du legst damit Implementierungsdetails frei. Dass ein Parameter in der Funktion nicht verändert werden darf, geht doch den Aufrufer nichts an. Das Offenlegen von Implementierungsdetails wird als schlechtes objektorientiertes Progammieren angesehen.

    Willst du also sichergehen, dass der Parameter stets denselben Wert hat, machst du das so:

    void fkt(int i)
    {
        const int& ci = i;
        // und erst jetzt beginnt die eigentliche Arbeit der Funktion.
    }
    


  • Sone schrieb:

    Wieso ist es so ein "Problem", wenn du verrätst dass du innen den Zeiger veränderst? Klar, du willst Implementierungsdetails geheim halten. Aber das ist doch wirklich etwas übertrieben...

    Weil es inkonsistent ist.

    Warum sind bei Funktion A die Parameter const und bei Funktion B nicht? Wenn du solche Sachen dann vor dir hast, ueberlegst du erstmal. Was macht B anders als A. Was vorallem dann lustig ist und dich auf vollkommen falsche Faehrten fuehrt, wenn du einen Bug suchst 😉

    Mal von dem Prinzip abgesehen, dass man Never Ever Ever Implementierungsdetails verraet.


Anmelden zum Antworten