Error Handling

Throwing and Catching Exceptions

A function doing a calculation finds an input that can't be handled. We all know, for example, that division by zero is not allowed. But how does the function inform the programmer of this specific problem? In general, it doesn't, and the program either continues on through the garbage left behind or crashes.

The following is a very simple division function which makes the assumption that the second argument is always nonzero.

#include <iostream>

int divide(int a, int b)
{
    return a / b;
}

int main()
{
    std::cout << divide(10, 2) << '\n';
    return 0;
}

The function takes two integers, a and b, and returns the result of dividing them. The calling of the function inside main supplies two numbers, 10 and 2, so it is printed 5. The code will work only as long as the statement about b is valid.

When the second argument is zero, the division is undefined.

#include <iostream>

int divide(int a, int b)
{
    return a / b;
}

int main()
{
    std::cout << divide(10, 0) << '\n';
    return 0;
}

The program may blow up, return an incorrect number, or seem to hang (without giving any feedback), depending on the platform. Nothing in a structured way for divide to announce that it cannot go on.

That's where an exception comes in. The function can throw an object describing the problem, and a matching handler somewhere else can catch the object and decide what to do.

#include <iostream>

int divide(int a, int b)
{
    if (b == 0)
        // Signal the error by throwing an exception
        throw 0;
    return a / b;
}

int main()
{
    try
    {
        std::cout << divide(10, 0) << '\n';
    }
    catch (int)
    {
        std::cout << "division by zero\n";
    }
    return 0;
}

Within divide the expression b == 0 is checked. If the test is true, then the expression throw 0; instantiates an object of temporary integer with value 0 and starts exception processing. The running of divide is halted immediately; the return statement is not run. Control is transferred to the closest matching handler.

In main, the call to divide is placed inside a try block. The catch (int) clause following is an exception handler that can catch any exception object of type int. Since the thrown object is of type int, then this handler is chosen.

The handler body simply prints the message and the program continues after the entire try catch construct.

The net result is that the division by zero is reported rather than producing undefined behavior.

The same function can return a value carrying more information.

#include <iostream>

int divide(int a, int b)
{
    if (b == 0)
        throw b;
    return a / b;
}

int main()
{
    try
    {
        std::cout << divide(10, 0) << '\n';
    }
    // Handle the matching exception here
    catch (int value)
    {
        std::cout << "cannot divide by " << value << '\n';
    }
    return 0;
}

The statement throw b; still throws an int. However, it is now the actual divisor that would have had the problem. The handler declares a parameter called value of type int. When the handler is entered, value gets a copy of the thrown object, so the message can use the real number that was zero.

One or more handlers may follow a single try block. Handlers are searched through in order; the first handler with a matching type is chosen.

#include <iostream>

int divide(int a, int b)
{
    if (b == 0)
        throw 0;
    if (a < 0)
        throw -1;
    return a / b;
}

int main()
{
    try
    {
        std::cout << divide(-8, 2) << '\n';
    }
    catch (int value)
    {
        if (value == 0)
            std::cout << "division by zero\n";
        else
            std::cout << "negative numerator\n";
    }
    return 0;
}

Now the function returns 0 for a zero divisor and -1 for a negative numerator. In the call, a negative numerator has been provided so -1 is returned. The sole int handler in the function gets that and prints the message accordingly.

If there is no handler that matches the thrown type, then std::terminate will be called and the program terminates. A catch all handler will be specified using an ellipsis:

#include <iostream>

int divide(int a, int b)
{
    if (b == 0)
        throw 0;
    return a / b;
}

int main()
{
    try
    {
        std::cout << divide(10, 0) << '\n';
    }
    catch (...)
    {
        std::cout << "unknown problem\n";
    }
    return 0;
}

Ellipsis matches every type. In this program, the thrown int is caught by the catch all and the generic message is printed. Catch all handlers are normally placed last as they will match all types and prevent any later handler from being considered.

Thus, an exception is a controlled transfer of control from a point of exception detection to an exception handler capable of handling the problem. The throw expression constructs the exception object and begins the search for an appropriate catch clause. The try block indicates the portion of code over which the search for an appropriate catch clause is permitted to search. When the handler terminates, execution resumes at the statement following the entire try catch statement, as if the exception was handled by the handler.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.