loading...

Elegant Error Handling with C++

lucpattyn profile image Mukit, Ataul ・3 min read

Handling errors elegantly is something C++ was missing for a long time.
In the early days, we used a lot of techniques, such as returning HRESULT (in windows mainly) or passing an error object, fill it back with error code and messages etc.

You may say there always have been exceptions (throw catch..) from nearly day one (or two) of C++, but exceptions are exceptions, which should be sparingly used in extreme cases. I never liked exceptions for simple error handling because they actually affect and changes the flow of the program. So I was always looking for a standard way to solve it.

Finally, it seems C++ has provided an elegant and structured way of error handling, literally with structures :)
(More info at : https://en.cppreference.com/w/cpp/error/error_code)

Let's first have a look how the error handling code would look before jumping into how to do it!

Suppose I want to insert a record in a database and it throws an error, here is how it would look in the codes :

    std::error_code e = db.insert(key, value);
    // using Windows MessageBox in WIN32 showing an alert dialog
    ::MessageBox(NULL, e.message().c_str(), "Error Message", 0);
    // using console
    cout << "Error Message : << e.message.c_str() << " Error Code: " << e.value();

Here the error code e has a method called message that returns std::string which you can show as a human readable message and can also get the value of error code by simply calling e.value() (ex. int err = e.value();)

Now it's time to have a look at how you would form an error object based on your context and situation. The following code creates a error object useful to return database related errors that can occur in your code.

    struct db_err_cat : std::error_category{
            const char* name() const noexcept override{
                return "db_errors";
            }
            std::string message(int ev) const override{
                if(ev == 0){
                    return "ok";
                }
                // switch case would make the code look better tbh :)
                if(ev == 1){
                    return "db insert error";
                }else if(ev == 2){
                    return "db delete error";

                }else if(ev == 3){
                    return "db update error";
                }
                
                return "db unknown error";
            }
    };

    db_err_cat db_err;

    

Now that we have created a structure (read class) to return database (db) related errors, let's give a way to create an object based on that structure that returns a generic std::error_code object which can be used from the upper layers of your codes:

    std::error_code make_error_code(int e){
         return {e, orm_err};
     }

So, in your code if a function needs to return a database insert error,
simple return make_error_code(1);

For example,

void insertIntoDB(std::string key, std::string val){
    bool b = internalDB.insert(key, val); 
    if(!b){
          return make_error_code(1);  
    }
    ....
}

You may say why not simply use boolean values like the internalDB, but in real world programs you actually need more info than true or false if an operation is successful or not.

So either I show with message box in windows or show something like:
cout << indertIntoDB(key, val).message().c_str(), finally C++ has given a standard and structured way to deal with errors elegantly.

Notice that you simply need to use the std::error_code object all over your main app and basically return std::error_code from your own built libraries. It has become real easy to do error logging with minimum headache.

I hope the C++ community comes to an agreement about this elegant way for error handling.

Discussion

pic
Editor guide
Collapse
dataphil_lib profile image
Philip H.

That's a good option for functions that would not return anything real, except a success indication. For other functions I really like to use std::optional to handle possible failures. This does not offer a way to tell the caller what went wrong, but there a lots of situations where that is not necessary.

Collapse
lucpattyn profile image
Mukit, Ataul Author

Some may argue for throw .. catch and exception handling as a way for error handling, but my point of view is :
for functions which have equal chance of failure as success, exception handling is not the way to go

Collapse
mdabek profile image
Marek Dabek

throw catch exception should not be used as a replacement to the regular error handling. In the below presentation Bjarne describes rules for using error-codes and exceptions he is following:

youtu.be/VoHOLDdfDhk?t=2714