Return false oder throw exception



  • Hallo!
    Ich arbeite an einem Programm das massiv Daten lesen und parsen muss. Hierbei kommt es immer wieder zu "Fehlern" beim Parsen, da Dateien zerstört sind, fehlen oder nicht geöffnet werden können. Ist es hier sinnvoller ein simples "return false" zu verwenden, um den Programmfluss zu beenden oder eher ein Umstieg auf Exceptions? Das schöne bei den Exceptions schein mir, dass die Exception den Stack abbaut?!
    Danke und Gruß,
    Fritz

    PS:
    Habe soeben angefangen mich mit Exceptions zu beschäftigen, also bitte Nachsicht üben, wenn ich falsche Annahmen mache 😕



  • Tomahawk schrieb:

    Das schöne bei den Exceptions schein mir, dass ich die Exception den Stack abbaut?!

    Das passiert bei einem return auch.



  • Ja schon, aber ich muss den returnwert step-by-step durch den Stack wandern lassen.

    Kleine Anmerkung noch: Das Programm muss mit einer Fehlermeldung terminiert werden, wenn das Parsen nicht vollständig möglich ist.

    Beispiele:

    // Try to open a file.
    std::ifstream ifs(filename);
    if (ifs.fail()) {
      std::cerr << "Cannot find or open file: " << filename << "\n";
      return false;
    } 
    
    // Try to find a match.
    if (!boost::regex_match(input, what, expression)) {
       std::cerr << "Found mismatch: " << filename << " (" << line << "): " << input << "\n";
       return false;
    }
    


  • Wenn du exakte Fehlermeldungen haben willst, wie "error on line x col y", dann bleibt einem eh nicht viel mehr über, als Exceptions.



  • Tomahawk schrieb:

    Das schöne bei den Exceptions schein mir, dass die Exception den Stack abbaut?!

    👍
    Verwende an den Stellen Exceptions, an denen ein bestimmtes Token benötigt wird, und du ohne auch nicht mehr weiter machen kannst. Definiere aber Testfunktionen die die Fehlerbehandlung über Rückgabewerte regeln, damit die Exceptions nicht zu einem if-else werden.

    PS: Wirf keinen std::string oder so, sondern einen std::runtime_error. Wenn du noch einen Fehlercode brauchst, erbe von std::runtime_error.



  • Tomahawk schrieb:

    Das schöne bei den Exceptions schein mir, dass die Exception den Stack abbaut?!

    Ja, und:

    - dass man einer Exception mehr Info mitgeben kann als einen Errorcode
    - dass Funktionen, die Exceptions werfen, einen echten Rückgabewert haben können, den man nicht erst aus einem struct (Rückgabewert + Errorcode) raisfriemeln muss
    - dass man bei Exceptions nicht nach jeder Funktion den Rückgabewert abfragen und behandeln muss sondern alle kritischen Funktionsaufrufe in einen try-Block packt (oft ist es irrelevant, welche Funktion genau schiefgelaufen ist)
    - dass man Exceptions nicht aus versehen ignorieren kann wie z.B. Fehlercodes.



  • Hier mal zwei naive Lösungen. Lässt sich das eleganter machen, ohne eine eigene Exception Klasse zu implementieren, die von std::exception erbt? Es sind eben unterschiedliche Parameter, die ich dem Nutzer mitgeben muss, damit die Exception behoben werden kann.

    // Ziemlich blöd, da ich von std::exception keinen Gebrauch mache.
    try {
      if (!boost::regex_match(input, what, expression)) {
        std::cerr << "Mismatch: " << filename << " (" << line << "): " << input << "\n";
        throw std::exception();
      }
    } catch (const std::exception& e) {
      std::cerr << "Exception: " << e.what() << "\n";
    }
    
    // Noch blöder, da ich einen zusätzlichen std::ostringstream benötige.
    try {
      std::ostringstream errors;
      if (!boost::regex_match(input, what, expression)) {
        errors << "Mismatch: " << filename << " (" << line << "): " << input << "\n";
        throw std::exception();
      }
    } catch (const std::exception& e) {
      std::cerr << "Exception: " << errors.str() << "\n";
    }
    


  • Was spricht dagegen eine eigene Klasse dafür zu haben?

    throw ExceptionMismatch(filename, line, input);
    


  • Es gibt bereits abgeleitete Klassen von std::exception, z.B. std::runtime_error. Die haben dann auch einen Konstruktor, der einen String mit der Fehlermeldung nimmt:

    try {
      if (!boost::regex_match(input, what, expression)) {
        std::string msg("Mismatch: ");
        msg += filename + " (" + boost::lexical_cast<std::string>(line) + "): " + input + "\n";
        throw std::runtime_error(msg);
      }
    } catch (const std::exception& e) {
      std::cerr << "Exception: " << e.what() << "\n";
    }
    


  • Tomahawk schrieb:

    // Noch blöder, da ich einen zusätzlichen std::ostringstream benötige.
    try {
      std::ostringstream errors;
      if (!boost::regex_match(input, what, expression)) {
        errors << "Mismatch: " << filename << " (" << line << "): " << input << "\n";
        throw std::exception();
      }
    } catch (const std::exception& e) {
      std::cerr << "Exception: " << errors.str() << "\n";
    }
    

    Die Frage stellt sich nicht, da das da nicht kompiliert.

    Außerdem ist es reichlich sinnlos, den Exception-Mechanismus (Rücksprung über mehrere Methoden hinweg) auszuhebeln indem man throw und catch in derselben Methode hat, die noch dazu auf einen im übergeordneten Scope angesiedelten Error-String zugreift.



  • Danke für den Tipp mit std::runtime_error!

    hhhh schrieb:

    Was spricht dagegen eine eigene Klasse dafür zu haben?

    throw ExceptionMismatch(filename, line, input);
    

    Hmmm, es gibt etwa 20 verschiedene Fehlermeldungen mit verschiedenen Kombinationen von Parametern = 20 Klassen?

    pumuckl schrieb:

    ...
    std::string msg("Mismatch: ");
    msg += filename + " (" + boost::lexical_cast<std::string>(line) + "): " + input + "\n";
    ...
    

    Funktioniert das tatsächlich, bekomme so eine "cannot add two pointers" Fehlermeldung?

    Vielleicht ist folgende Lösung mit einem std::runtime_error am generischsten (KISS-Prinzip):

    std::ostringstream errors;
    ...
    try {
      std::ifstream ifs(filename);
      if (ifs.fail()) {
        errors << "Cannot find or open file: " << filename << "\n";
        throw std::runtime_error(errors.str()); 
      }
      if (!boost::regex_match(input, what, expression)) { 
        errors << "Found mismatch: " << filename << " (" << line << "): " << input << "\n"; 
        throw std::runtime_error(errors.str()); 
      } 
    } catch (const std::exception& e) { 
      std::cerr << "Exception: " << e.what() << "\n"; 
    }
    


  • Tomahawk schrieb:

    pumuckl schrieb:

    std::string msg("Mismatch: ");
    msg += filename + " (" + boost::lexical_cast<std::string>(line) + "): " + input + "\n";
    

    Funktioniert das tatsächlich, bekomme so eine "cannot add two pointers" Fehlermeldung?

    Kommt drauf an, was filename (und evtl. input) sind. Ich war davon ausgegangen, dass du benannte String-Variablen auch tatsächlich als Strings verwaltest, nicht als char-Pointer.



  • Tomahawk schrieb:

    Hmmm, es gibt etwa 20 verschiedene Fehlermeldungen mit verschiedenen Kombinationen von Parametern = 20 Klassen?

    Nein. Es stellt sich zwar die Frage, warum es so viele verschiedene Parameterkombinationen gibt, aber den Konstruktor zu überladen scheint mir doch wesentlich sinnvoller.



  • cooky451 schrieb:

    Verwende an den Stellen Exceptions, an denen ein bestimmtes Token benötigt wird, und du ohne auch nicht mehr weiter machen kannst. Definiere aber Testfunktionen die die Fehlerbehandlung über Rückgabewerte regeln, damit die Exceptions nicht zu einem if-else werden.

    PS: Wirf keinen std::string oder so, sondern einen std::runtime_error. Wenn du noch einen Fehlercode brauchst, erbe von std::runtime_error.

    Nur mal so off-topic: Was sind denn exceptions, die nicht runtime_errors sind? Also beispielsweise "throw compile_time_error". Oder "throw error_before_program_runs" 😃 .



  • ich bins schrieb:

    Was sind denn exceptions, die nicht runtime_errors sind?

    Schau doch einfach in die Standard-Referenz deiner Wahl. Beispielsweise logic_error. Wird zwar auch erst zur Laufzeit geworfen und gefangen, besagt aber, dass beim Coding Blödsinn passiert ist. runtime_error sind Fehler, die zur Compilerzeit nicht verhindert werden können.



  • pumuckl schrieb:

    Tomahawk schrieb:

    pumuckl schrieb:

    std::string msg("Mismatch: ");
    msg += filename + " (" + boost::lexical_cast<std::string>(line) + "): " + input + "\n";
    

    Funktioniert das tatsächlich, bekomme so eine "cannot add two pointers" Fehlermeldung?

    Kommt drauf an, was filename (und evtl. input) sind. Ich war davon ausgegangen, dass du benannte String-Variablen auch tatsächlich als Strings verwaltest, nicht als char-Pointer.

    <filename> und <input> sind schon std::string Objekte, aber " (" oder "): " sind doch char-Pointer bzw. werden als solche Fehlerquelle ausgegeben. However, es kann eben keinen überladenen Stringoperator geben, der zwei chars "addiert".



  • Tomahawk schrieb:

    However, es kann eben keinen überladenen Stringoperator geben, der zwei chars "addiert".

    Mal sehen:
    filename + " (" + boost::lexical_caststd::string(line) + "): " + input + "\n";
    -> string + char* + string + char* + string + char*
    = string + string + char* + string + char*
    = string + char* + string + char*
    = string + string + char*
    = string + char*
    = string

    Wo sind da zwei char* nebeneinander, wenn filename und input wirklich keine char* sind?



  • pumuckl schrieb:

    Tomahawk schrieb:

    However, es kann eben keinen überladenen Stringoperator geben, der zwei chars "addiert".

    Mal sehen:
    filename + " (" + boost::lexical_caststd::string(line) + "): " + input + "\n";
    -> string + char* + string + char* + string + char*
    = string + string + char* + string + char*
    = string + char* + string + char*
    = string + string + char*
    = string + char*
    = string

    Wo sind da zwei char* nebeneinander, wenn filename und input wirklich keine char* sind?

    OK, das passt, wenn es tatsächlich immer mind. ein std::string zwischen zwei char* steht. Aber darauf kann ich mich in meinen Exeption-Routinen nicht verlassen und daher sind mir die std::ostringstreams robuster.



  • @Tomahawk
    So lange du mit einem std::string anfängst, sollte es funktionieren. Auch sowas wie string + char* + char* + char*.
    Weil "+" halt von links nach rechts ausgewertet wird.



  • Nachfolgender Code ist compilierbar. Wenn ich allerdings die auskommentierten Zeilen aktiviere bekomme ich folgende Fehlermeldung:

    sstream(603): error C2248: 'std::basic_ios<_Elem,_Traits>::basic_ios' : cannot access private member declared in class 'std::basic_ios<_Elem,_Traits>'

    Woran das liegt kann ich mir im Moment nicht erklären. Es könnte mit der Instanzierung in throw exception(filename) zu tun zu haben 😕

    #include <fstream>  // NOLINT
    #include <iostream>  // NOLINT
    #include <string>
    
    class Exception : public std::exception {
    public:
      explicit Exception(const std::string& filename) {
        str = filename;
        // oss << filename;
      }
    
      const char* what() const {
        return str.c_str();
        // return oss.str().c_str();
      }
    
    private:
      std::string str;
      // std::ostringstream oss;
    };
    
    int main() {
      std::string filename("some.txt");
      try {
        throw Exception(filename);
      } catch (const std::exception& e) {  // NOLINT
        std::cerr << "Exception: " << e.what() << "\n";
      }
      return 0;
    }
    


  • Tomahawk schrieb:

    bekomme ich folgende Fehlermeldung:

    Das ist nur ein Teil der Fehlermeldung. Bei templates ist das leider ziemlich mühsam, den eigentlichen Fehler zu finden. Schau mal weiter runter bis du findest, wo in deinem Code die Ursache für den fehler liegt.[/quote]


Anmelden zum Antworten