Error Handling

Exceptions

  • Exceptions provide a way to react to exceptional circumstances (like runtime errors) in programs by transferring control to special functions called handlers.

  • To catch  exceptions, a portion of code is placed under exception inspection. This is done by enclosing that portion of code in a try -block. When an exceptional circumstance arises within that block, an exception is thrown that transfers the control to the exception handler. If no exception is thrown, the code continues normally and all handlers are ignored.

  • An exception is thrown by using the throw  keyword from inside the try  block. Exception handlers are declared with the keyword catch, which must be placed immediately after the try  block:

  • A throw  expression accepts one parameter (in this case the integer value 20), which is passed as an argument to the exception handler.

  • The exception handler is declared with the catch  keyword immediately after the closing brace of the try  block. The syntax for catch  is similar to a regular function with one parameter. The type of this parameter is very important, since the type of the argument passed by the throw  expression is checked against it, and only in the case they match, the exception is caught by that handler.

  • After an exception has been handled the program, execution resumes after the try -catch block, not after the throw  statement.

#include <iostream>

int main () {
    try
    {
        throw 20;
    }
    catch (int e)
    {
        std::cout << "An exception occurred. Exception Nr. " << e << '\n';
    }
    return 0;
}
  • Multiple handlers (i.e., catch expressions) can be chained; each one with a different parameter type. Only the handler whose argument type matches the type of the exception specified in the throw statement is executed.

  • If an ellipsis (...) is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown. This can be used as a default handler that catches all exceptions not caught by other handlers:

try {
    // code here
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }
  • It is also possible to nest try -catch blocks within more external try  blocks. In these cases, we have the possibility that an internal catch block forwards the exception to its external level. This is done with the expression throw ; with no arguments.

try {
    try {
        // code here
    }
    catch (int n) {
        throw;
    }
}
catch (...) {
    std::cout << "Exception occurred";
}

Standard Exceptions

  • std::exception  in the <exception>  header.

  • All exceptions thrown by components of the C++ Standard library throw exceptions derived from this exception class. These are:

    • bad_alloc

      • thrown by new on allocation failure

    • bad_cast

      • thrown by dynamic_cast when it fails in a dynamic cast

    • bad_exception

      • thrown by certain dynamic exception specifiers

    • bad_typeid

      • thrown by typeid

    • bad_function_call

      • thrown by empty function objects

    • bad_weak_ptr

      • thrown by shared_ptr when passed a bad weak_ptr

    • logic_error

      • error related to the internal logic of the program

    • runtime_error

      • error detected during runtime

  • This class has a virtual member function called what  that returns a null-terminated character sequence (of type char * ) and that can be overwritten in derived classes to contain some sort of description of the exception.

#include <iostream>
#include <exception>

class my_exception: public std::exception {
    virtual const char* what() const throw()
    {
        return "My exception happened";
    }
} my_ex;

int main () {
    try {
        throw my_ex;
    } catch (std::exception& e) {
        std::cout << e.what() << '\n';
    }
    return 0;
}

Dynamic Exception Specification (deprecated in C++14, removed in C++17)

  • From C++11 onward, they were already discouraged in favor of noexcept , which remains the supported mechanism for specifying exception behavior.

  • Syntax:

    void f() throw(int);
    
    double my_function (char param) throw (int);
    
  • A dynamic exception specification follows the declaration of a function, appending a throw specifier to it.

  • If this function throws an exception of some type other than int, the function calls std::unexpected  instead of looking for a handler or calling std::terminate .

  • If this throw specifier is left empty with no type, this means that std::unexpected  is called for any exception. Functions with no throw specifier (regular functions) never call std::unexpected , but follow the normal path of looking for their exception handler.

int my_function (int param) throw(); // all exceptions call unexpected
int my_function (int param);         // normal exception handling