Skip to content

Exceptions vs. Error Codes

Consistent error handling is a key feature of a high quality software library. Until recently, the mesh processing library I’m working on did a poor job in this regard. This gave me the opportunity to revisit C++ error handling. I took a few notes in between. Hope this helps you choosing an error handling strategy in your C++ projects.

There are two major approaches to error handling in C++:

  1. Error codes
  2. Exceptions

I’ll briefly introduce both approaches and highlight their pros and cons.

Error Codes

The traditional way to report errors is by using error codes1. They come in various forms, such as returning a simple bool or int value:

bool foo() { return true; }
int bar() { return 667; }

This basic form of error reporting relies on convention: What does it mean to return 667? Something good or bad? That’s up for interpretation and documentation. The error code does not convey any meaning by itself.

More well-engineered error reporting uses an enum or similar to provide symbolic constants that convey meaning about what type of error occurred:

enum ReturnType { SUCCESS = 1, SOLVE_FAILED = 2, BAD_INPUT = 3};

A major drawback of error codes is that they lead to function calls being intertwined with if..else blocks testing for the various error conditions.

int ret = func();
if (ret == SUCCESS) {
    printf("yay!");
}
else if (ret == SOLVE_FAILED) {
    printf("nay!");
}
else if (ret == BAD_INPUT) {
    printf("meh!");
}

The result is that program logic is mixed up with error handling. This is not a big issue in the simple example above, but it quickly becomes confusing in real-world code.

Exceptions

Exceptions are a more modern alternative to error codes. An exception is an error generated at runtime which is propagated through the call stack until it is either caught or the program terminates. Here’s a simple example:

#include <stdexcept>
#include <iostream>

void foo() {
    throw std::runtime_error("error!");
}

void bar() {
    foo();
}

int main(void) {
    try {
        bar();
    }
    catch (const std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
    }
}

Pros

Here are the biggest advantages I see in using exceptions:

Cleaner code. Exceptions can be thrown at any point in the program, including class constructors which don’t return any value. Exceptions are independent of the return type of a function. The code catching the exception can be separate from the location the error occurred, thereby avoiding intertwining of application logic and error handling.

Proper types. Exceptions use proper types checked by the compiler. You can create your own exception classes to give the error additional semantic meaning and to pass information about the error with the exception. A practical example from mesh processing would be to include information about the location of the error so that you could highlight the problematic region in your visualization.

Harder to ignore. Function return values can be silently ignored. Exceptions need to be handled explicitly, otherwise the program will terminate. You are free to ignore an exception locally as long as it is taken care of higher up the call stack.

Standardization. You can’t avoid exceptions if you are using the standard library. The C++ Core Guidelines recommend the use of exceptions. Using exceptions is a modern C++ best practice.

Cons

Here are my top three drawbacks of exceptions:

Visibility. Exceptions are not directly visible in the source code. You can’t see what exceptions can be thrown by a function by looking at its signature. Even looking at the implementation does not reveal which exceptions could be thrown by other functions called in the code.

Control flow. Exceptions are an abrupt change in control flow, much like a goto statement. This can make it difficult to reason about the control flow of the program, making it harder to debug the code.

Implementation effort. Exceptions need special care when implementing them. Otherwise, you risk subtle errors such as memory leaks. Following the RAII2 idiom is a key guideline here.

Conclusion

Personally, I like the simplicity of return codes. It’s a straightforward approach. You can teach it to a beginner in no time. This is not the case with exceptions. Doing exceptions right is a challenge. I’m not even sure I’m getting things right in PMP right now.

However, I feel that the advantages outweigh the disadvantages. If you are developing a library that adheres to modern C++ best practices, exceptions are the method of choice for error handling3.

References and Further Reading

Check out the following references if you are interested in more concrete guidelines on writing exception-safe code:

  1. I use the term ‘error codes’ instead of ‘return codes’ because the codes could be reported differently than through a function return value, for example via a global error status variable. 

  2. RAII stands for “resource acquisition is initialization” and means that resources are acquired during object initialization and released during object destruction. See the Wikipedia article for more information. 

  3. There are a few exceptions, though (no pun intended). Special purpose code like hard real-time systems might have different constraints that prohibit the use of exceptions.