c++qttry-catch

Is it good practice to use try-catch with logging, throwing user defined types that are not runtime errors?


I am writing code that reads files, parses them, and stores the data into a database when a user clicks a button. I have separated each operation into functions within multiple classes, but many of these functions make database queries. For example:

// in ui class cpp
void btnClicked(){
    const QStringList files = ...; // select files to load
    FileHandler handler;
    int succ = handler.loadFromFiles(files);
    if(succ != files.size()) // when not all files are loaded
        logging(...);
}

// FileHandler.cpp
class FileHandler {
public:
    int loadFromFiles(...) {
        read(...);  // N queries
        myParser->parse(...);  // M queries
        myDBHandler->insert(...);  // O queries
    }
private:
    Parser* myParser;
    DBHandler* myDBHandler;
};

To ensure reliability, I want to verify that each query executes successfully and log any errors if issues occur. I could doing something like this:

// during each query
QSqlQuery query("SELECT * FROM myTable");
if(!query.isActive()) log(query.lastError());

However, since multiple classes are involved, this would lead to redundant error-handling code across different classes, and additional unnecessary logs to be written because already logging the number of files loaded successfully.

Therefore, I am considering throwing a QSqlError in case of a failure and catching it within the user input event function to log the error. The structure would look like this:

// ui class cpp
void btnClicked(){
    const QStringList files = ...; // select files to load
    FileHandler handler;
    int succ = 0;
    try { 
        succ = handler.loadFromFiles(files);
    } catch (const QSqlError& e) {
        logging(e.text());
        return;
    }
    if(succ != files.size()) // when not all files are loaded
        logging(...);
}

// during each query
QSqlQuery query("SELECT * FROM myTable");
if(!query.isActive()) throw query.lastError();

However, I am not very familiar with using try-catch blocks, so I am unsure if this structure is appropriate. I’ve ensured that all resources are properly released and that the main event loop remains intact, and I understand that SQL query failures are not critical runtime errors. However, many programs and documents recommend throwing objects derived from std::exception and terminating the program in the catch block.

Is this usage considered a good practice?


Solution

  • By all means, if you have the code where you would need to propagate the errors along your call stack, throwing the exception is the good solution.

    Exaggerated example, instead of having:

    int dbAccessFail() {
        std::cerr << "failed\n";
        return -1;
    }
    
    int foo() {
        // calling db
        int errCode = dbAccessFail();
        if (errCode != 0) { /* error handling */ return errCode; }
        // more ...
        return 0;
    }
    
    int bar() {
        // something ...
        int errCode = foo();
        if (errCode != 0) { /* error handling */ return errCode; }
        // more ...
        return 0;
    }
    

    which clearly doesn't scale very well, you should have:

    void dbAccessFail() {
        std::cerr << "failed\n";
        throw std::runtime_error("failed");
    }
    
    void foo() {
        // calling db
        dbAccessFail();
        // more ...
    }
    
    void bar() {
        // something ...
        foo();
        // more ...
    }
    

    and then finally the caller of bar can decide to catch the exception and handle it, e.g.:

        try {
            bar();
        } catch(const std::exception& e) {
            std::cerr << "exception: " << e.what() << '\n';
            // some cleanup, retry, error dialog, or whatever
        }
    

    Then in your specific case, you must be careful to not allow the exception escape the Qt event loop, so it would be a good idea to have try-catch block in every slot calling potentially throwing methods. Otherwise it is considered Undefined Behavior, not just std::terminate.

    Don't throw QSqlError, instead throw any class that inherits std::exception, e.g. throw std::runtime_error(query.lastError().text().toStdString()); or you can play with QException (I never tried them, though).