When exceptions occur, the programmer has to decide a strategy according to which he would handle the exceptions. The strategies could be, displaying the error messages on the screen, or displaying a dialog box in case of a GUI environment, or requesting the user to supply better or simply terminating the program execution. When a function detects an exceptional situation, you represent this with an object. This object is called an exception object. In order to deal with the exceptional situation you throw the exception. This passes control, as well as the exception, to a designated block of code in a direct or indirect caller of the function that threw the exception. This block of code is called a handler. In a handler, you specify the types of exceptions that it may process and will pass control to the first appropriate handler that is able to process the exception thrown. When this happens, an exception is caught. A handler may rethrow an exception so it can be caught by another handler. Usually C programmers deal with exceptions in two ways: Following the function calls with error checks on return values to find whether the function did its job properly or not. Using the setjmp and longjmp mechanism. This approach is intended to intercept and handle conditions that do not require immediate program termination. For example, if a recursive decent parser detects an error, it should report it and continue with further processing. Let us look at these methods more closely. Checking Function Return Value In C programs a function usually returns an error value if an error occurs during execution of that function. For example, file-opening functions return a NULL indicating their inability to open a file successfully. Hence, each time we call these functions we can check for the return value. This is shown for some fictitious functions func1( ), func2( ) and func3( ) in the following code: Code: if ( func1( ) == ERROR_VALUE ) // handle the error else // do normal things if ( func2( ) == NULL ) // handle the error else // do normal things if ( func3( ) == -1 ) // handle the error else // do normal things There are three problems with this approach: Every time we call a function we must check its return value through a pair of if and else. Easier said than done! Surrounding every function call with a pair of if and else results in increase in code size. Also, too many if-elses make the listing lose its readability. This approach cannot be used to report errors in the constructor of a class as the constructor cannot return a value. It becomes difficult to monitor the return values in case of deeply nested function calls. setjmp( ) and longjmp( ) C does not provide an easy way to transfer control out of a function expect by returning to the expression that called the function. For the vast majority of function calls, this is a desirable limitation. You want the discipline of nested function calls and returns to help you understand the flow of control through a program. Nevertheless, on some occasions that discipline is too restrictive. The program is sometimes easier to write, and to understand, if you can jump out of one or more function invocations at a single stroke. You want to bypass the normal function returns and transfer control to somewhere in an earlier function invocation. For example, you may want to return to execute some code for error recovery no matter where an error is detected in your application. The setjmp( ) and the longjmp( ) functions provide the tools to accomplish this. The setjmp( ) function saves the state or the context of the process and the longjmp( ) uses the saved context to revert to a previous point in the program. What is the context of the process? In general, the context of a process refers to information that enables you to reconstruct exactly the way the process was at a particular point in its flow of execution. When setjmp( ) is called from main( ) it stores all the relevant information about the current processor state in jmp_buf and returns zero. In this case, the if statement is satisfied and the process( ) function is called. If something goes wrong in process( ) (indicated by the flag variable), we call longjmp( ) with two arguments: the first is the buffer that contains the context to which we will return. When the stack reverts back to this saved state, and the return statement in longjmp( ) is executed, it will be as if we were returning from the call to setjmp( ), which originally saved the buffer buf. The second argument to longjmp( ) specifies the zero so that in the if statement we can tell whether the return is induced by a longjmp( ). The setjmp( )/longjmp( ) combination enables us to jump unconditionally from one C function to another without using the conventional return statements. Essentially, setjmp( ) marks the destination of the jump and longjmp( ) is a non-local goto that executes the jump. However, this approach is not appropriate for an object-oriented environment because it does not properly handle the destruction of objects. In our program when longjmp( ) returned the destructor of the sample class did not get called. Hence the object could not get properly cleaned up. This is the critical reason why a better alternative should be thought of for handling exceptions in C++. Exception Handling in C++ C++ provides a systematic, object-oriented approach to handling run-time errors generated by C++ classes. The exception mechanism of C++ uses three new keywords: throw, catch, and try. Also, we need to create a new kind of entity called an exception class. Suppose we have an application that works with objects of a certain class. If during the course of execution of a member function of this class an error occurs, then this member function informs the application that an error has occurred. This process of informing is called throwing an exception. In the application we have to create a separate section of code to tackle the error. This section of code is called an exception handler or a catch block. Any code in the application that uses objects of the class is enclosed in a try block. Errors generated in the try block are caught in the catch block. Code that doesn’t interact with the class need not be in a try blocks. It is not a working program, but it clearly shows how and where the various elements of the exception mechanism are placed. Code: class sample { public : // exception class class errorclass { } ; void fun( ) { If ( some error occurs ) throw errorclass( ) ; // throws exception } } ; // application void main( ) { // try block try { samples s ; s.fun( ) ; } catch ( sample :: errorclass ) // exception handler or catch block { // do something about the error } } Here sample is any class in which errors might occur. An exception class called errorclass, is specified in the public part of sample. In main( ) we have enclosed part of the program that uses sample in a try block. If an error occurs in sample::fun( ) we throw an exception, using the keyword throw followed by the constructor for the errorclass: throw errorclass( ) ; When an exception is thrown control goes to the catch block that immediately follows the try block. The following is one more example of a function try block with a member initializer, a function try block and a try block: Code: #include <iostream> using namespace std; class E { public: const char* error; E(const char* arg) : error(arg) { } } ; class A { public: int i; // A function try block with a member // initializer A() try : i(0) { throw E("Exception thrown in A()"); } catch (E& e) { cout << e.error << endl; } } ; // A function try block void f() try { throw E("Exception thrown in f()"); } catch (E& e) { cout << e.error << endl; } void g() { throw E("Exception thrown in g()"); } int main() { f(); // A try block try { g(); } catch (E& e) { cout << e.error << endl; } try { A x; } catch(...) { } } The following is the output of the above example: Exception thrown in f() Exception thrown in g() Exception thrown in A() The constructor of class A has a function try block with a member initializer. Function f() has a function try block. The main() function contains a try block. You can declare a handler to catch many types of exceptions. The allowable objects that a function can catch are declared in the parentheses following the catch keyword (the exception_declaration). You can catch objects of the fundamental types, base and derived class objects, references, and pointers to all of these types. You can also catch const and volatile types. The exception_declaration cannot be an incomplete type, or a reference or pointer to an incomplete type other than one of the following: void* const void* volatile void* const volatile void* You cannot define a type in an exception_declaration. You can also use the catch(...) form of the handler to catch all thrown exceptions that have not been caught by a previous catch block. The ellipsis in the catch argument indicates that any exception thrown can be handled by this handler. If an exception is caught by a catch(...) block, there is no direct way to access the object thrown. Information about an exception caught by catch(...) is very limited. You can declare an optional variable name if you want to access the thrown object in the catch block. A catch block can only catch accessible objects. The object caught must have an accessible copy constructor.