Exception werfen



  • Also ich habe im Moment folgende Exception struktur und weiß nicht so recht ob das Beispiel am Anfang von Dravere sinnvoll für mich wäre:

    //irgendwo in main
    
    try {
    
       try {
    
           // in diesem Teil wird das hauptprogramm sozusagen angstoßen mit
           // allen sub_routinen(). 
           foo();
    
        } catch (std::runtime_error &err)
          {
              std::cerr << err.what() << std::endl;
              // es folgt noch einiges zum exit
          } 
    
    } catch (std::bad_alloc & err)
    {
        std::cerr << err.what() << std::endl;
        // es folgt noch einiges zum exit
    }
    

    in meinem foo() und allen subroutinen gibt es dann bis jetzt immer nur immer ein

    throw std::runtime_error("Hier steht dann meine Fehlermeldung");
    

    Ich überlege jetzt die eigene exceptionsklasse von dravere zu übernehmen jedoch weiß ich nicht genau ob es sinn machen würde...ich habe schließlich doch nur lauf-zeitfehler oder nicht? Und wenn dann unterscheidet sich doch z.B. auch ein fehler bei division durch Null nur durch die message die geworfen wird. Wo ist der Unterschied? Ist meine bisherige struktur eher Blödsinn und wenn ja wie würdet ihr das schreiben?



  • Kommt halt drauf an, wie stark du abstrahieren und unterscheiden willst. Wenn du sowieso alle Fehler gleich behandelst (kann ja sein), lohnt es sich auch nicht, für jeden Fehlertypen eine neue Klasse zu schreiben. Wenn du aber zum Beispiel noch zusätzliche Informationen in den Exceptions mittragen willst, kann sich eine eigene Klasse bewähren, da man da halt viel freier ist.

    Warum machst du das so umständlich? Ich meine jetzt nicht mal die Einrückung, sondern die Verschachtelung der try s. Machs doch einfach so:

    try
    {
        // Fehleranfälliger Code
    }
    catch (std::runtime_error& e)
    {
        // Behandle Laufzeitfehler
    }
    catch (std::bad_alloc& e)
    {
        // Behandle Allokationsfehler
    }
    


  • ah das wußte ich noch nicht.
    Nur noch mal ne frage. Allein nur an der message würde sich was ändern...also der string sozusagen. Da machts doch wenig sinn da ne exception klasse mit unterschiedlichen exceptions zu bauen oder?



  • gambo schrieb:

    Ich überlege jetzt die eigene exceptionsklasse von dravere zu übernehmen jedoch weiß ich nicht genau ob es sinn machen würde...ich habe schließlich doch nur lauf-zeitfehler oder nicht? Und wenn dann unterscheidet sich doch z.B. auch ein fehler bei division durch Null nur durch die message die geworfen wird. Wo ist der Unterschied? Ist meine bisherige struktur eher Blödsinn und wenn ja wie würdet ihr das schreiben?

    Schreiben siehe Nexus post.

    Was den Sinn angeht, ich halte verschiedene Erbende Exceptions für durchaus sinnvoll. Der Punkt daran ist der du weißt was fliegt, du kannst passende aussagekräftige Klassennamen nehmen. Der Vorteil daran ist außerdem der, wenn du mit deinen nachrichten Exceptions wirfst, dann fliegt nur eine, aber du mußt die Strings immer vergleichen, hast du aber Exceptionklassen dann weißt du anhand der Klasse genau was Sache ist und kannst direkt loslegen, außerdem gibt das den catch Blöcken etwas mehr Struktur.

    throw std::runtime_error("Device Lost");
    throw std::runtime_error("Datei nicht gefunden");
    
    throw e_MyException_Device("Device (hier Name eintragen) Lost");
    throw e_MyException_File("Datei (hier Name eintragen) nicht gefunden");
    

    Hier müsstest du oben imer die gleiche Nachricht nutzen, wärend du unten siehst was der Fehler ist und die Nachricht damit individuelelr sien kann, was sieht schöner aus?



  • Tachyon schrieb:

    Die Funktion muss immer nur einen konstanten Zeiger auf die aktuelle C-String-Repräsentation des Strings zurückgeben.

    Genau, zusammenhängendend und mit '\0' am Ende.

    Wenn nicht mit mutable, externen oder static-Sachen gebastelt wird, also c_str() keine neue Kopie eines Buffers anfertigen kann (weil dann ja keiner aufräumen kann), muss hier

    string s = "foo";
    char& c = s[2]; // (1)
    c = '.';        // (2)
    s.c_str();      // (3)
    

    bei (1) schon eine Referenz auf ein Element eines zusammenhängenden Null-terminierten Buffers zurückgegeben werden. Schließlich bekommt die string-Instanz nicht mit, wenn ich bei (2) etwas ändere, muss aber immer einen Null-terminierten String mit den Änderungen bereithalten. Und das geht eben nur, wenn direkt in den zusammenhängenden, Null-terminierten String geschrieben wird, der bei c_str() zurückgegeben wird.



  • gambo schrieb:

    } catch (std::runtime_error &err)
          {
              std::cerr << err.what() << std::endl;
              // es folgt noch einiges zum exit
          }
    

    exit ist in so einer Situation nicht unbedingt der Weisheit letzter Schluß. Statische Objekte werden so nicht mehr destruiert und das Programm auf die harte Tour beendet, da kann man gleich abort() oder terminate() aufrufen.

    Man sollte aus dem Hauptprogramm mittels "return EXIT_FAILURE;" rausgehen, das ist sauber und ordentlich.
    So sähe das besser aus:

    int main () {
        try {
            foo ();
        }
        catch (std::exception &e) {
            ...
            return EXIT_FAILURE;
        }
        catch (...) {
            ...
            return EXIT_FAILURE;
        }
    }
    


  • Badestrand schrieb:

    ...

    Okay, da ist was dran. Wie wärs dann mit mutable ? So gesehen in einer Implementation eines C++-Compilers für DSPs, bei dem der eigentliche String in einer deque-artigen Blockstruktur verwaltet wird, also nicht zusammenhängend. Der Puffer für den C-String ist mutabel und wird beim Aufruf von c_str() angepasst.
    Grund dafür war laut Hersteller das etwas merkwürdig gehandhabte Speichermanagement.

    PS: Das macht im Übrigen auch sinn, da mutable genau für solche Fälle gedacht ist: Die Änderung von Membern, ohne den Status des eigentlichen Objekts zu beeinflussen.



  • Badestrand schrieb:

    bei (1) schon eine Referenz auf ein Element eines zusammenhängenden Null-terminierten Buffers zurückgegeben werden

    Ne.

    c_str() ist eine Moment aufnahme. Jeder call zu einer nicht const Methodes des strings invalidiert den zeiger den dir c_str() geliefert hat.



  • throw e_MyException_Device("Device (hier Name eintragen) Lost");
    throw e_MyException_File("Datei (hier Name eintragen) nicht gefunden");
    

    Muss ich so nicht für jede unterschiedliche Exception eine neue Klasse entwerfen?



  • gambo schrieb:

    Muss ich so nicht für jede unterschiedliche Exception eine neue Klasse entwerfen?

    ja, die 5 zeilen code wirst du aber schon noch hinbekommen... kannst es ja auch deine ide generieren lassen.

    viele unterschiedliche klassen ist gut. denn wie willst du unterschiedliche fehler erkennen wenn alles nur "irgendein fehler" ist? dann kommt das tolle "es ist ein unbekannter fehler aufgetreten" heraus. doof sowas.



  • gambo schrieb:

    Muss ich so nicht für jede unterschiedliche Exception eine neue Klasse entwerfen?

    Ja, du musst die Möglichen fehler anschaun und Ordnen udn danach Klassen erstellen, die sind auch meist sehr klein. Der Vorteil an der sache ist du kanst auchmal Spezielel Daten mitgeben bzw einen richtigen text mitgeben den du dann zB in ein Log ausgeben kannst. Und wie Shade schon sagte es gibt da nichts schlimmeres als ein Ergebniss alla "Es ist ein Fehler aufgetreten, leider wissen wir nicht was passiert ist".


  • Administrator

    Wegen unterschiedlichen Klassen bei unterschiedlichen Fehlern:
    Wenn die Informationen, welche mitgetragen werden, gleich bleiben, was haltet ihr eigentlich von so einem Lösungsansatz?

    template<int>
    class BasicExceptionWithMessage
      : public std::exception // oder was auch immer.
    {
      // ... usw.
      std::string m_message;
      // ... usw.
    };
    
    // Also wir haben eine grundlegende Schablone für Exceptions.
    // Danach:
    typedef BasicExceptionWithMessage<0> MathException;
    typedef BasicExceptionWithMessage<1> IOException;
    typedef BasicExceptionWithMessage<2> // usw.
    // usw. ...
    

    Grüssli



  • Dravere schrieb:

    ...was haltet ihr eigentlich von so einem Lösungsansatz?

    Finde ich okay, und habe ich auch schon gemacht. Allerdings mit einem enum, damit man sprechendere Tags hat.



  • Tachyon schrieb:

    Wie wärs dann mit mutable ? So gesehen in einer Implementation eines C++-Compilers für DSPs, bei dem der eigentliche String in einer deque-artigen Blockstruktur verwaltet wird, also nicht zusammenhängend. Der Puffer für den C-String ist mutabel und wird beim Aufruf von c_str() angepasst.
    Grund dafür war laut Hersteller das etwas merkwürdig gehandhabte Speichermanagement.

    Das macht Sinn und bei nicht-Desktop-Anwendungen würde ich mich auf 0-terminiert bei op[] auch nicht verlassen.

    Shade Of Mine schrieb:

    Badestrand schrieb:

    bei (1) schon eine Referenz auf ein Element eines zusammenhängenden Null-terminierten Buffers zurückgegeben werden

    c_str() ist eine Moment aufnahme. Jeder call zu einer nicht const Methodes des strings invalidiert den zeiger den dir c_str() geliefert hat.

    Vielleicht hättest du dir den kompletten Post durchlesen sollen.



  • hui...interessant...

    Finde ich okay, und habe ich auch schon gemacht. Allerdings mit einem enum, damit man sprechendere Tags hat.

    meinst du dann sowas wie eine klassen-instantiierung über z.B.

    BasicExceptionWithMessage<Math> MyExcept;
    

    ??



  • Dravere schrieb:

    Wegen unterschiedlichen Klassen bei unterschiedlichen Fehlern:
    Wenn die Informationen, welche mitgetragen werden, gleich bleiben, was haltet ihr eigentlich von so einem Lösungsansatz?

    Garnix, da kann man keine vernünftigen hierachien einbauen und hierachien sind das was exceptions toll machen. sonst kann man ja gleich integer werfen...

    ich will IOError fangen können oder aber BadBlockError oder FileLockedError. Diese hierachien sind das was exceptions besser machen als error codes.


  • Administrator

    Shade Of Mine schrieb:

    Dravere schrieb:

    Wegen unterschiedlichen Klassen bei unterschiedlichen Fehlern:
    Wenn die Informationen, welche mitgetragen werden, gleich bleiben, was haltet ihr eigentlich von so einem Lösungsansatz?

    Garnix, da kann man keine vernünftigen hierachien einbauen und hierachien sind das was exceptions toll machen. sonst kann man ja gleich integer werfen...

    ich will IOError fangen können oder aber BadBlockError oder FileLockedError. Diese hierachien sind das was exceptions besser machen als error codes.

    Schau dir meinen Vorschlag nochmals an. Du kannst immer noch Hierarchien aufbauen und es ist nicht das gleiche, wie wenn du einen Integer wirfst, da man bei mir unterschiedliche Typen hat.

    Grüssli



  • Badestrand schrieb:

    Vielleicht hättest du dir den kompletten Post durchlesen sollen.

    Ich verstehe den Post aber nicht.
    Warum sollte keiner aufräumen können? das string objekt hat ownership, im dtor wird aufgeräumt.

    es wirkt so als denkst du, dass du mit &str[0] einen zeiger auf den string bekommen könntest, und das ist halt falsch...

    oder willst du was anderes sagen?



  • Dravere schrieb:

    Schau dir meinen Vorschlag nochmals an. Du kannst immer noch Hierarchien aufbauen und es ist nicht das gleiche, wie wenn du einen Integer wirfst, da man bei mir unterschiedliche Typen hat.

    Und wie baut man hierachien auf?

    das einfachste ist der präprozessor, wenn man keine IDE verwendet die das für einen macht:

    IMPLEMENT_EXCEPTION_2(BadBlockError, IOException, "Bad Block at "<<param1<<" in "<<param2);
    

    und das wird zu:

    class BadBlockError : public IOException {
    private:
      string msg;
    public:
      template<typename Param1, typename Param2>
      BadBlockError(Param1 param1, Param2 param2)
      : msg(build_error_message(param1, param2) {
      }
    
      template<typename Param1, typename Param2>
      static string build_error_message(Param1 param1, Param2 param2) {
        stringstream s;
        s<<"Bad Block at "<<param1<<" in "<<param2;
        return s.str();
      }
    //... what(), etc.
    };
    

    Das hat zB auch den vorteil dass ich jederzeit und überall eine neue exception klasse anlegen kann - während man bei deiner methode sich sehr schwer tut, da es zu kollisionen kommen kann.

    idR ist so ein zähl template unbrauchbar...


  • Administrator

    Shade Of Mine schrieb:

    Und wie baut man hierachien auf?

    In dem man neue Klassen erstellt? Bei mir ging es nur um gleiche Exceptions, also auch in der gleichen Hierarchiestufe, zusammen zu fassen, bzw. die Schreibarbeit dafür zu minimieren. Ich will nicht alle Exceptions durch die BasicExceptionWithMessage ersetzen.

    Deine Möglichkeit mit dem Makro dagegen, ist natürlich etwas weiterentwickelt. So extrem viel wollte ich gar nicht vereinfachen. Allerdings die folgende Aussage, finde ich nicht ganz korrekt.

    Shade Of Mine schrieb:

    ... während man bei deiner methode sich sehr schwer tut, da es zu kollisionen kommen kann.

    Auch bei deiner Lösung mit dem Makro musst du aufpassen, da kann es ebenfalls schnell zu einer Kollision kommen. Kollisionen sind immer möglich, mit ein paar Tricks kann man sie aber verhindern, auch bei meiner Lösungsmöglichkeit.

    Shade Of Mine schrieb:

    idR ist so ein zähl template unbrauchbar...

    Sie haben durchaus ihren Einsatzzweck.

    Grüssli


Anmelden zum Antworten