The C++ Programming Language

Part 11: Error handling

Errors in programming are the standard. However careful we may be there comes a time that our code encounters situations it cannot handle. Sometimes we are dealing with numbers hat lead to numerical errors and other times the program comes across a file it cannot read. All these situations may lead to a crash. C++ has built-in tools that can help us catch these errors and handle them gracefully.

Writing robust code

The first step to error handling is adopting techniques that make our code robust and leave very few chances to errors. Here are some good programming habits.

Reuse code

Always break your code down to small and simple functions. Test them extensively and reuse them in your projects. They can become the building blocks of a stable and robust program. Here is a simple function that calculates the value of a polynomial:

double polynomial(const std::vector<double>& coeffs, double x) {
    size_t p = coeffs.size() - 1;  // the maximum power of x
    double val = 0;
    for (auto it = coeffs.begin(); it != coeffs.end(); ++it) {
        val += (*it) * pow(x, p);
        --p;  // as we go the power of x decreases
    }
    return val;
}

This could be a part of a mathematics library in a physics simulation program or a game. Extensive use and testing will guarantee that it is a safe piece of code to use.

Use reliable external libraries

As consequence of the previous is the selection of reliable external libraries. The first library we rely on is the C++ Standard Library. For 3D graphics we rely on OpenGL and its successor Vulkan. The dedicated work behind the creation of these libraries and their use by others like you is guarantee that these libraries have been extensively tested and are error free.

Validate your data

Validate your data as early as possible in your code. Make sue that there is nothing there that could lead to a crash. Validating at each step is not so easy and it leads to very slow code. Making sure that our data are clear of unwanted values helps us write simple and efficient code being sure that no error will occur.

Test your code

Design and develop test mechanisms in your code that will run and test it under the harder conditions automatically and report any errors that might come up. Run these tests systematically, every night if possible, to make sure that your code responds as expected under any conditions.

Exceptions

Exceptions in C++, provide a robust way to handle errors that arise during program execution. When a division by zero occurs or a file fails to open the system raises an exception. C++ has a built-in mechanism that can catch exceptions. C++ Standard Library creates a programmer friendly interface to this mechanism.

try / catch

try / catch is the building block of exception handling in C++. The basic syntax is like this:

try {
    code that might cause problems
}
catch (...) {  // catch all exceptions
    code to perform clean-up if an error occurs
}

We place our code that might run into problems in the try block. Should anything go wrong the code automatically resumes in the catch block. There we usually put code to perform any cleanup before we return from our function.

Here is a simple example:

void exception_s()
{
    int i = -1;
    try {
        char* buf = new char[i]; // bad allocation exception
        std::cout << "this should not be seen\n";
    }
    catch (...) {  // catch all exceptions
        std::cout << "simple_exception catches exception" << "\n";
    }
    std::cout << "exception_s terminates gracefully!\n";
}

Going one step further we can get some information about the error that occurred:

void exception2_s()
{
    int i = -1;
    try {
        char* buf = new char[i]; // bad allocation exception
        std::cout << "this should not be seen\n";
    }
    catch (const std::exception& e) {
        std::cout << "exception2_s catches exception:" << e.what() << "\n";
    }
    std::cout << "exception2_s terminates gracefully!\n";
}

Using the generic exception, we catch everything (remember polymorphism and virtual functions) and we can get specific information about the error.

Catching specific exceptions

We can have multiple catch blocks in exception handling, so that we can take different actions depending on the error. In the following example the function generates two different errors and uses different catch blocks to handle them individually:

void throw_some_exception(){
    throw std::bad_array_new_length();  // raise a random exception
}

void exception3_s(int ec){
    int i = -1;
    try {
        if (ec == 1)
            throw_some_exception();
        else
            char* buf = new char[i]; // bad allocation exception
        std::cout << "this should not be seen\n";
    }
    catch (const std::bad_array_new_length& e) {
        // the code resumes here after the bad_array_new_length exception
        std::cout << "exception3_s specific exception:" << e.what() << "\n";
    }
    catch (const std::exception& e) {
        // the code resumes here after any other exception
        std::cout << "exception3_s general exception:" << e.what() << "\n";
    }
    std::cout << "exception3_s terminates gracefully!\n";
}

Throwing custom exceptions

We can create and throw custom exceptions in programs too. Using exceptions to break from situations that may lead to crashes is a good but very difficult technique. It requires very good knowledge of the overall architecture of the application, careful planning and above all sticking to the plan or things will get out of hand very soon with unpredictable results.

class custom_exception : public std::exception {
    std::string what_s;
public:
    custom_exception(const char* msg = "this is a custom exception") :std::exception(), what_s(msg){

    }
    virtual const char* what() const throw() {
        return what_s.c_str();
    }
};

void custom_s(int ex) {
    try {
        if (ex == 1)
            throw custom_exception();
        else
            throw std::bad_array_new_length(); // raise a random exception
    }
    catch (const custom_exception& e) {
        std::cout << "custom_s specific exception:" << e.what() << "\n";
    }
    catch (const std::exception& e) {
        // the code resumes here after any other exception
        std::cout << "custom_s general exception:" << e.what() << "\n";
    }
    std::cout << "custom_s terminates gracefully!\n";
}

Summary

Here we tried to approach the topic of writing stable and robust applications. The main topics were:

The C++ Programming Language