sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English

Show/Hide search bar
black cat logo variable logo
[2 Nov 2011]

Exceptions in C++

Basics

To handle errors in C++ you can use system of error codes that are returned by methods, or by using system of global variables-states, or... In each case main part of the code intersects with error handling part of the code. To separate main code and error handling code you can use exceptions. Exceptions arise in exceptional erroneus situations during application's runtime, e.g., during access to non-existent element of a container (like std::vector).

There are three C++ keywords that are used for work with exceptions: try, catch and throw.
  • try - defines block of code in which exceptions should be handled. If exception arises in try block, then it will be handled by following catch blocks.
  • catch - defines block of code that is responsible for processing of exceptions. There may be multiple consecutive catch blocks for different types of exceptions.
  • throw - creates exception. Type of exception is equal to type of expression right after throw keyword. For example throw 5; creates exception of type int, and throw "help"; creates exception of type const char *.
  • try{ // indicates that this block may throw exception
        throw "Exception message"; // throws exception with type const char *
    }
    catch( int & e ){ // handling for exception with type int
        std::cout << e;// response for exception, e.g., message to console
    }
    catch( const char* & e ){ // handling for exceptons with type const char *
        std::cout << e; // response for exception, e.g., message to console
    }
    catch(...){ // handling for exceptions with any type
        std::cout << "Unknown exception";
    }

    And here is working example. For example, access to non-existent member of the vector:

    std::vector A; // empty vector
    try{
        A.at(0); // access to non-existent member of vector.
        // Exception will be thrown from somewhere inside at() method.
        // A[0] won't generate exception, because operator []
        // doesn't check bounds of container during access.
    }
    catch(std::exception & e){ // handling for exceptions with type std::exception ++
        std::cout<< e.what(); // message to console: vector::_M_range_check
    }

    When exception is thrown, application starts to execute catch blocks. If exception is thrown in function, and error handling code is outside of that function, application will exit body of the function and unroll stack until it won't find catch block. Stack unrolling is performed correctly, so all variables that are allocated on the stack will be automatically deleted. But variables that are allocated on the heap won't be deleted (use smart pointers to fix this). For example:

    void func(){ // function that throws exceptions
        classCat cat1; // object on the stack
        Cat *cat2 = new Cat(); // object on the heap
        throw "test"; // exception starts unrolling of the stack
        Cat *cat3 = new Cat(); // won't be executed
    }

    int main(){
        try{
            func();
        }
        catch(...){} // exception from func() will be handled here
    }
    Exception ouside of try block: terminate() function

    If exception is thrown outside of any try block and not handled by any catch block, then application will crash. For example:

    std::vector A; // empty vector
    A.at(0); // exception without try block

    You will see following error in the console: terminate called after throwing an instance of 'std::out_of_range'. This error means that exception haven't been handled and because of this application have called terminate(). Function terminate() ends execution of application by call to abort() function. You can subsitute terminate() function with another function with help of set_terminate(). Set_terminate() requires only one input argument and it's pointer to function. For example, to stop execution of application without error you can substitute terminate() function and stop application without abort() function (by exit() function): #include <exception>
    void customTerminate(){ // substitution for termanate() function
        std::cout << "Unhandled exception!!!" << std::endl;
        exit(0); // stop execution without error
    }
    int main(){
        std::set_terminate(customTerminate); // substitute terminate() function
        throw "Test";
    }

    Hierarchy of exceptions in standard template library

    STL contains following hierarchy of exceptions:

    exception // base type for STL exceptions
        bad_alloc // exception during allocation of memory
        bad_cast // exception during dynamic_cast
        bad_typeid // exception during call to typeid
        bad_exception // for exceptions that weren't handled by any catch block
        logic_failure // exceptions that may be detected by code review
            domain_error // math exceptions, e.g., root from neg number
            invalid_argument // when argument is invalid
            lenght_error // errors during resize of string, vector, etc.
            out_of_range // access to non-existant element of container
        runtime_error // exceptions that can only be identified during runtime
            overflow_error, underflow_error // mathematical errors
            range_error // errors during internal calculation of ranges
    This hierarchy is created for possibility to logically separate different exceptions and their handling. Exception of type out_of_range is child of logic_failure exception, and logic_failure in turn is child of std::exception basic type. For example catch(std::exception) handles all STL exceptions, but catch(std::out_of_range) handles only failures during access to containers:
    try{
        // code that generates different exceptions
    }
    catch(std::out_of_range & e){
        // exception handling for failures during access to containers
    }
    catch(std::exception & e){
        // exception handling for all STL exceptions
    }
    catch(...){
        // handling for all other exceptions
    }
    You can create your own hierarchy of exceptions by using std::exception as basic class, or by creation of your own class for basic exception. In fact you can throw any object as exception. For example, graphicsException will be child of std::exception. If graphicsException won't be catched by catch(graphicsException &e) statement, it will be catched by catch(std::exception &e) block:
    struct graphicsException : public std::exception{ // new exception as child of STL
        const char * what () const { // message of new exception
            return "Graphics exception";
        }
    };

    void exceptionTest(){
        try {
            throw graphicsException();
        }
        catch(graphicsException& e) {
            std::cout << e.what();
        }
        catch(std::exception& e) {
            std::cout << "Not graphicsException";
        }
    }
    Handling of exception of unknown type

    Often programmer doesn't know all types of possible exceptions that are generated by third-party API. In such situation you can use function like exceptionProcessor(), which performs general exception handling. This function also allows to separate all catch blocks from main code into one place. ExceptionProcessor() uses throw keyword without arguments. Throw without argument rethrows current exception. Following code shows how exceptionProcessor() works:

    void exceptionProcessor(){ // general exception handling
        try {
            throw; // throw without arguments, rethrows exception
            // that have been arised in exceptionTest2() function
        }
        catch (std::exception & e) {
            // handling of STL exceptions
        }
        catch (const char * & e) {
            // handling of string exceptions
        }
        catch (int & e) {
            // handling of integer-type exceptions
        }
        // here you can put catch blocks for any possible type of exceptions
        catch(...){
            // Unrecognized exception
        }
    }

    void exceptionTest2(){
        try {
            // code with possible exceptions
        }
        catch (...) { // all exceptions will be handled by this catch block
            exceptionProcessor(); // and it calls exceptionProcessor
        }
    }

    Try block may contain try blocks, and that try blocks can contain another try blocks. Sometimes it's required to return from inner try block to outer one. This can be accomplished by throw keyword without arguments. Such throw in catch block leads to unrolling of the stack, up to outer try-catch block. In such the way you can create multiple levels of exception handling. It can be useful, when some action deep inside a code has thrown exception, and it's required to react to this exception on different levels of application to correctly resume work. For example:

    void exceptionThrower(){
        try{ // inner try block
            throw 1;
        }
        catch(...){ // inner block of exception handling
            std::cout << "Inner catch";
            throw; // rethrow exception to outer try block
        }
    }

    void exeptionTest3(){
        try{ // outer try block
            exceptionThrower();
        }
        catch(...){ // outer block of exception handling
            std::cout << "Outer catch";
        }
    }
    Exception in constructor

    Exceptions in constructor of an object are safe. If constructor throws exception, then object won't be created, and there's no need to call destructor for it. All members of the object that were created on the stack will be automatically deleted. But members that were created in the heap won't be deleted, as object isn't created, and as result destructor won't be called. Such members should be wrapped with smart pointers to be deleted properly.

    Exception in destructor

    Exceptions in destructor are not safe. They can lead to following situation. Imagine that object (destructor of this object generates exceptions) is created on the stack. Then during exectution of other actions an exception arises. Stack unwinding process deletes all objects that were created on the stack, and as result destructor of the object generates new exception. Now application has two active exceptions. In such case app doesn't now what to do. It can handle one exception, but another will be unhandled. In best cases this behavior leads to memory leaks. But C++ doesn't handle any of these exceptions and calls terminate() function (and application crashes...). Here is small example:

    struct Cat{
    ~Cat(){ throw "Ups..."; } // exception in destructor
    };

    void exceptionThrower(){
        try{
            Cat a; // object in the stack
            throw 1; // exception leads to deletion of objects from stack
        }
        catch(...){
            std::cout << "Hm"; // app will crash before this
        }
    }

    So if there's exceptional situation in destructor, then a correct action is to handle error without exceptions, e.g., output message to log file. It's also possible to throw exception in destructor if you are sure that there won't be any other exceptions during handling of exception from destructor.

    Polymorphic types of exceptions

    As was mentioned above, you can throw any object as exception. It's easy to handle simple non-polymorphic objects - such exception will be handled by catch block with type that corresponds to object. But what will happen with polymorphic object?

    struct Animal{}; // base class
    struct Cat:public Animal{}; // derived class

    void exceptionsTest4(){
        Cat a; // create instance of derived object
        Animal &b = a; // and assign it to base class variable
        try{
            throw b; // throw by base class variable
        }
        catch(Cat & e){} // No, types don't match
        catch(Animal & e){} // this catch block will handle exception
    }

    Exception will be handled by catch(Animal & e){} block. So what's wrong? When you are using polymorphic objects, you should throw polymorphic exceptions. This can be done in the following way:

    struct Animal{ // base class
    public:
        virtual void raise(){ throw *this; } // polymorphic exception
    };

    struct Cat:public Animal{ // derived class
    public:
        virtual void raise(){ throw *this; } // polymorphic exception
    };

    void exceptionsTest(){
        Cat a;
        Animal &b = a;

        try{
            b.raise();
        }
        catch(Cat & e){} // this catch block will handle exception
        catch(Animal & e){}
    }
    Advices and useful snippets
    Catch exceptions by reference. If exception is catched by value, then it may be coppied. If exception is catched by pointer, then it's not clear who should delete it. So it's better to catch exception by reference.
    Check for successful memory allocation (bad_alloc):
    try(){
        int *pb = new int[999999];
    }
    catch(std::bad_alloc & e){ /* handle bad allocation error */ }
    Check for successful memory allocation without exceptions:
    int *pb = new (std::nothrow) int[999999];
    if(pb==0) { /* not allocated */ }
    Pros of exceptions
  • Code is easier to understand, it's less expensive and development is faster.
  • Separation of main code and error handling code.
  • It's hard to ignore exceptions - they will terminate application.
  • Automatic creation of code for handling of exceptions.
  • Any object can be passed as exception.
  • It's possible to create logical hierarchy of exceptions.
  • Stack variables are deleted correctly.
  • Error codes are not possible in constructors.
  • Exception can easily provide additional information: __LINE__, __FILE__, descripion of error.
  • Cons of exceptions
  • Application becames larger and slower.
  • Performance of exceptions depends on implementation.
  • Memory leaks are possible if smart pointer aren't used.
  • Exceptions are harder for beginners. It's not easy to analyze complicated structure of exceptoins.
  • Exceptions aren't good for real-time applications (because of bigger number of checks).
  • It's hard to integrate exceptions in old projects.



  • Sun and Black Cat- Igor Dykhta (igor dykhta email) 2007-2014