ADVERTISEMENT

Exception Handling

ADVERTISEMENT

Exceptions are the way of flagging unexpected conditions or errors that have occurred in C++ program.

Exception Mechanism

so far we have handled error conditions by using the if statement to test some expressions and then executing specific code to deal with the error. C++ Language provides a good mechanism to tackle these conditions. The exception mechanism
uses three keywords.

  1. try – It identifies a code block in which an exception can occur
  2. catch – Identifies a block of code in which the exception is handled
  3. throw – It causes an exception condition to be originated

Introduction to Exception Handling

In this tutorial we will be discussing some aspects of C++ exception handling. To start this discussion, let’s consider a simple example. Suppose that you are writing a program to manipulate calendar dates, and want to check whether a given year is in the 20th century.

Using exceptions, one way to do this might be:

#include <IOSTREAM.H>
class DateException {
       char* err; public:
       DateException(char* s) {err = s;}
void print() const {cerr << err <<
endl;
}
}; // a function that operates on dates
 void g(int date) { if (date < 1900) throw DateException("date < 1900");
 if (date > 1999) throw
DateException("date > 1999"); // process date ...
} // some code that uses
dates void f()
 { g(1879); }
 int main()
 { try { f(); }
 catch (const DateException& de)
 { de.print(); return 1; } return 0; }

The basic idea here is that we have a try block:

try
{
 f();
}

Within this block, we execute some code, in this case a function call f().
Then we have a list of one or more handlers:

catch (DateException de) {
 de.print();
 return 1;
}

If an abnormal condition arises in the code, we can throw an exception:

if (date < 1900)
 throw DateException("date < 1900");

and have it caught by one of the handlers at an outer level, that is, execution will continue at the point of the handler, with the execution stack unwound.

An exception may be a class object type such as DateException, or a fundamental C++ type like an integer. Obviously, a class object type can store and convey more information about the nature of the exception, as illustrated in this example. Saying:

throw -37;

will indeed throw an exception, which may be caught somewhere, but this idiom is not particularly useful.

What if the handler we declare is changed slightly, as in:

catch (DateException* de) {
 de->print();
    return 1;
}

In this case, because an object of type DateException is thrown, rather than a DateException* (pointer), no corresponding handler will be found in the program. In that case, the runtime system that handles exception processing will call a special library function terminate(), and the program will abort. One way to avoid this problem is to say:

main()
{
  try {
  body_of_program();
}
catch (...) {
   // all exceptions go through here
   return 1;
}
return 0;
}

where “…” will catch any exception type.

We will explore various details of exception handling later, but one general comment is in order. C++ exceptions are not the same as low-level hardware interrupts, nor are they the same as UNIX signals such as SIGTERM. And there’s no linkage  between exceptions such as divide by zero (which may be a low-level machine exception) and C++ exceptions.

Throwing An Exception

In the last issue we introduced C++ exception handling. In this issue we’ll go more into detail about throwing exceptions.

Throwing an exception transfers control to an exception handler. For example:

        void f()
        {
                throw 37;
        }

        void g()
        {
                try {                   // try block
                        f();
                }
                catch (int i) {         // handler or catch clause
                }
        }

In this example the exception with value 37 is thrown, and control passes to the handler. A throw transfers control to the nearest handler with the appropriate type. “Nearest” means in the sense of stack frames and try blocks that have been dynamically entered.

Typically an exception that is thrown is of class type rather than a simple constant like “37”. Throwing a class object instance allows for more sophisticated usage such as conveying additional information about the nature of an exception.

A class object instance that is thrown is treated similarly to a function argument or operand in a return statement. A temporary copy of the instance may be made at the throw point, just as temporaries are sometimes used with function argument passing. A copy constructor if any is used to initialize the temporary, with the class’s destructor used to destruct the temporary. The temporary persists as long as there is a handler being executed for the given exception. As in other parts of the C++ language, some compilers may be able in some cases to eliminate the temporary.

An example:

        #include <iostream.h>

        class Exc {
                char* s;
        public:
                Exc(char* e) {s = e; cerr << "ctor called\n";}
                Exc(const Exc& e) {s = e.s; cerr << "copy ctor called\n";}
                ~Exc() {cerr << "dtor called\n";}
                char* geterr() const {return s;}
        };

        void check_date(int date)
        {
                if (date < 1900)
                        throw Exc("date < 1900");

                // other processing
        }

        int main()
        {
                try {
                        check_date(1879);
                }
                catch (const Exc& e) {
                        cerr << "exception was: " << e.geterr() << "\n";
                }

                return 0;
        }

If you run this program, you can trace through the various stages of throwing the exception, including the actual throw, making a temporary copy of the class instance, and the invocation of the destructor on the temporary.

It’s also possible to have “throw” with no argument, as in:

        catch (const Exc& e) {
                cerr << "exception was: " << e.geterr() << "\n";
                throw;
        }

What does this mean? Such usage rethrows the exception, using the already-established temporary. The exception thrown is the most recently caught one not yet finished. A caught exception is one where the parameter of the catch clause has been initialized, and for which the catch clause has not yet been exited.

So in the example above, “throw;” would rethrow the exception represented by “e”. Because there is no outer catch clause to catch the rethrown exception, a special library function terminate() is called. If an exception is rethrown, and there is no exception currently being handled, terminate() is called as well.

In the next issue we’ll talk more about how exceptions are handled in a catch clause.

Stack Unwinding

In the last issue we talked about throwing exceptions. Before discussing how exceptions are handled, we need to talk about an intermediate step, stack unwinding.

The exception handling mechanism is dynamic in that a record is kept of the flow of program execution, for example via stack frames and program counter mapping tables. When an exception is thrown, control transfers to the nearest suitable handler. “nearest” in this sense means the nearest dynamically surrounding try block containing a handler that matches the type of the thrown

exception. We will talk more about exception handlers in a future issue. Transfer of control from the point at which an exception is thrown to the exception handler implies jumping out of one program context into another. What about cleanup of the old program context? For example, what about local class objects that have been allocated? Are their destructors called?

The answer is “yes”. All stack-allocated (“automatic”) objects allocated since the try block was entered will have their destructors invoked. Let’s look at an example:

        #include <iostream.h>

        class A {
                int x;
        public:
                A(int i) {x = i; cerr << "ctor " << x << endl;}
                ~A() {cerr << "dtor " << x << endl;}
        };

        void f()
        {
                A a1(1);

                throw "this is a test";

                A a2(2);
        }

        int main()
        {
                try {
                        A a3(3);

                        f();

                        A a4(4);
                }
                catch (const char* s) {
                        cerr << "exception: " << s << endl;
                }

                return 0;
        }

Output of this program is:

        ctor 3
        ctor 1
        dtor 1
        dtor 3
        exception: this is a test

In this example, we enter the try block in main(), allocate a3, then call f(). f() allocates a1, then throws an exception, which will transfer control to the catch clause in main().

In this example, the a1 and a3 objects have their destructors called. a2 and a4 do not, because they were never allocated.

It’s possible to have class objects containing other class objects, or arrays of class objects, with partial construction taking place followed by an exception being thrown. In this case, only the constructed subobjects will be destructed.

Handling an Exception

In previous issues we discussed throwing of exceptions and stack unwinding. Let’s now look at actual handling of an exception that has been thrown. An exception is handled via an exception handler. For example:

        catch (T x) {
                // stuff
        }

handles exceptions of type T. More precisely, a handler of the form:

        catch (T x) {
                // stuff
        }

or:

        catch (const T x) {
                // stuff
        }

or:

        catch (T& x) {
                // stuff
        }

or:

        catch (const T& x) {
                // stuff
        }

will catch a thrown exception of type E, given that:

        - T and E are the same type, or

        - T is an unambiguous public base class of E, or

        - T is a pointer type and E is a pointer type that can be
        converted to T by a standard pointer conversion

As an example of these rules, in the following case the thrown exception will be caught:

        #include <iostream.h>

        class A {};

        class B : public A {};

        void f()
        {
                throw B();
        }

        int main()
        {
                try {
                        f();
                }
                catch (const A& x) {
                        cout << "exception caught" << endl;
                }

                return 0;
        }

because A is a public base class of B. Handlers are tried in order of appearance. If, for example, you place a handler for a derived class after a handler for a corresponding base class, it will never be invoked. If we had a handler for B after A, in the example above, it would not be called. A handler like:

        catch (...) {
                // stuff
        }

appearing as the last handler in a series, will match any exception type.

If no handler is found, the search for a matching handler continues in a dynamically surrounding try block. If no handler is found at all, a special library function terminate() is called, typically ending the program.

An exception is considered caught by a handler when the parameters to the handler have been initialized, and considered finished when the handler exits.

In the next issue we’ll talk a bit about exception specifications, that are used to specify what exception types a function may throw.

Terminate() And Unexpected()

Suppose that you have a bit of exception handling usage, like this:

        void f()
        {
                throw -37;
        }

        int main()
        {
                try {
                        f();
                }
                catch (char* s) {
                }

                return 0;
        }

What will happen? An exception of type “int” is thrown, but there is no handler for it. In this case, a special function terminate() is called. terminate() is called whenever the exception handling mechanism cannot find a handler for a thrown exception. terminate() is also called in a couple of odd cases, for example when an exception occurs in the middle of throwing another exception.

terminate() is a library function which by default aborts the program. You can override terminate if you want:

        #include
        #include 

        typedef void (*PFV)(void);

        PFV set_terminate(PFV);

        void t()
        {
                cerr << "terminate() called" << endl;
                exit(1);
        }

        void f()
        {
                throw -37;
        }

        int main()
        {
                set_terminate(t);

                try {
                        f();
                }
                catch (char* s) {
                }

                return 0;
        }

Note that this area is in a state of flux as far as compiler adaptation of new features. For example, terminate() should really be “std::terminate()”, and the declarations may be found in a header file “”. But not all compilers have this yet, and these examples are written using an older no-longer-standard convention.

In a similar way, a call to the unexpected() function can be triggered by saying:

        #include
        #include 

        typedef void (*PFV)(void);

        PFV set_unexpected(PFV);

        void u()
        {
                cerr << "unexpected() called" << endl;
                exit(1);
        }

        void f() throw(char*)
        {
                throw -37;
        }

        int main()
        {
                set_unexpected(u);

                try {
                        f();
                }
                catch (int i) {
                }

                return 0;
        }

unexpected() is called when a function with an exception specification throws an exception of a type not listed in the exception specification for the function. In this example, f()’s exception specification is:

        throw(char*)

A function declaration without such a specification may throw any type of exception, and one with:

        throw()

is not allowed to throw exceptions at all. By default unexpected() calls terminate(), but in certain cases where the user has defined their own version of unexpected(), execution can continue.

There is also a brand-new library function:

        bool uncaught_exception();

that is true from the time after completion of the evaluation of the object to be thrown until completion of the initialization of the exception declaration in the matching handler. For example, this would be true during stack unwinding (see newsletter #017). If this function returns true, then you don’t want to throw an exception, because doing so would cause terminate() to be called.

ADVERTISEMENT
M. Saqib: Saqib is Master-level Senior Software Engineer with over 14 years of experience in designing and developing large-scale software and web applications. He has more than eight years experience of leading software development teams. Saqib provides consultancy to develop software systems and web services for Fortune 500 companies. He has hands-on experience in C/C++ Java, JavaScript, PHP and .NET Technologies. Saqib owns and write contents on mycplus.com since 2004.
Related Post