EXCEPTION HANDLING
An exception is an abnormal condition that arises in a source code sequence at run time. in other words an exception is a run time error and exception handling is a mechanism that solves run time errors by using three keywords: try, catch and throw. In this article we will see how to throw an exception at the position where an exception is encountered. We will also see multiple catch handlers, nested try blocks and how to rethrow an exception. After this we will see how to catch an uncaught exception and how to specify an exception. In last we will look at a more practical example of an exception handling.
Basic Concept of Exception Handling
An exception condition is a problem that prevents the continuation of the method or scope you are in. It can be outright errors that cause the program to fail or condition that can be lead to errors. In C++, exception handling provides an elegant way that allows two separately developed methods to communicate when a program encounters an exception, that is errors in an orderly, organized and consistent manner. In this article we discuss how exceptions are handled, that is how to raise, or throw an exception at the position where the program encounters an exception.
Actually there should be a clear distinction between an exception condition and a normal problem. In a normal problem, user has enough information to cope with the difficulty. As far as an exception condition is concerned, user can not continue processing until user does not have the information to deal with the problem. Therefore you can say that exceptional handling allows one part of a program to sense and dispatch errors conditions, and another part to handle there errors.
There are various reasons that may cause some exceptions, as listed below:
1. Stack overflow
2. Divide-by-zero
3. Unable to open a file
4. Violating the boundary of an array
5. Falling short of memory
6. Attempting to initialize an object to an impossible value
When such an exception occurs, all we can do is jump out of the current part and transfer control to some other part of the program that is designed to deal with that particular exception. This is what happens when we throw an exception. Actually selection of a strategy for handling an exception depends upon the circumstances.
Some programs write error message on the console; some display dialog box in a graphical user interface; still others request the user to enter better data; and others terminate the program. The choices differ because an application can detect some basic errors whereas it can not be expected to detect all possible errors.
The exception handling in C++ is provided by using three reserved words - try, catch and throw. The significance of these reserve words will be discussed after some minutes. Firstly we will discuss how can we handle with such exceptions.
Exception Handling in C
Traditional C programmers make use of two approaches to exception handling. Either they follow each function call with a test for errors or they use setjmp() and longjmp() function to intercept and handle error conditions that do not require immediate program termination. The first approach makes use of some global macro such as errno and NULL or error function returns values to report the nature of the error.
For example, let we are using the malloc() function. When we use the malloc() function, then there are two possibilities.
- either a block of memory is allocated and a pointer to the allocated block is returned back.
- or a null pointer if it is unable to allocate memory space.
Hence each time we call this function, we can check for the return value. This functionality is illustrated as:
int *ptr;
ptr = (int *) malloc(50*2);
if (ptr == NULL)
{
printf("Unable to allocate memory.");
getch();
exit(0);
}
Therefore the burden of checking for this exceptional condition is on the caller. Whenever we call malloc() function, it is essential to repeat similar block of code over and over again. No doubt it results in increase in its body and also too many if-elses make the listing difficult to read, it is also difficult to trace out the return values in case of deeply nested function calls.
Another limitation of this approach is that we can not use this technique to report errors in the constructor of a class because the constructors are not allowed to return value.
These problems are overlooked by the second approach, that is by using setjmp() and longjmp() approach. The setjmp() and longjmp() functions make use of a structure called jmp-buf defined in the header file "setjmp.h". Actually the setjmp() and longjmp() approach is intented to intercept and handle conditions that do not require immediate program termination. In this approach, when an exception is encountered it would jump from one function to another. The program uses setjmp() to identify that place and longjmp() to get back to it.
Following code fragment illustrates the use of setjmp() and longjmp() mechanism.
// Program-lohit1.cpp
#include
#include
jmp_buf buf;
void main( )
{
void Sample();
void ReportError();
int err;
err = setjmp(buf);
if (err ==1)
ReportError();
else
Sample();
}
void Sample()
{
int error=1;
// Perform some activity
if (error == 1)
longjmp(buf,1);
}
void ReportError()
{
cout<<" \n An error has occurred";
}
Here when the main() function encounters a setjmp() call, it saves the current contents of a process in jmp-buf structure and returns 0. After execution of setjmp() function, it calls simple() function. If Sample( ) function encounters some sort of errors then the longjmp(), the counter part of setjmp(), is called. The longjmp() function has two arguments. One is the buffer name that contains the context to which control will return and second specifies the return value to be used during this return. It should be non-zero.
The longjmp()call unwinds the stack to its state as recorded in the jump-buf structure by the setjmp() call. Thus the setjmp()/ longjmp() functions transfers control unconditionally from one C function to another without using the return statements. Here the setjmp() specifies the destination of jump and longjmp() is a non-local goto that executes the jump.
However this approach solves the exceptions to some extent, but unfortunately it contains anomalies. Suppose that the Sample( ) function looks like this:
void Sample()
{
int error=0;
char *ptr;
FILE *fptr;
fptr=fopen(filename, "r");
ptr=(char*) malloc(100);
// Perform some activity
error=1;
// If it encounters any error
if (error==1)
longjmp(buf,1);
free(ptr);
fclose(fptr);
}
This function calls fopen() and malloc() functions and allocate memory prior to longjmp()call and release this memory after longjmp() call. Here the point is that the longjmp() call occur prior to memory released by free() function and fclose() function.
Therefore when it encounters an exception, it does not release memory occupied by fptr and ptr. Each time you call this function it allocates memory for fptr and ptr. Thus it may get soon out of memory space. A simple programmer solves this problem by structuring his programs to avoid it.
An alternative approach is to solve this problem is that place the longjmp() call after the free() and fclose() calls. But remember that the situation is not always so simple. Similar type of problem you face in an object oriented environment. In C++ a constructor allocates memory from a heap and a destructor deallocates memory and returns it to the heap.
Here the object did not get properly destroyed.
void Sample
{
Distance dist(10,40.25);
// Perform some activity
error =1;
if (error)
longjmp(buf,1);
}
Here the distance class allocates memory for its two variables. One is an int and another is a float. Its destructor returns that memory to the heap. Here the destructor does not execute at all because the longjmp() call transfer the control to the setjmp() call before the dist object goes out of scope. That's why the object could not get properly cleaned up.
This problem is one that C++ exception handling solves.
Exception Handling in C++
We know that exceptions are runtime errors that a program may detect and require immediate handling by the program. C++ provides an elegant and systematic object oriented approach to handle run time errors generated by C++ classes. C++ exception handling is managed via three new keywords: try, catch and throw.
The try Block
The try block consists of those statements that may generate some sort of exceptions. A try block looks like this.
try
{
// C++ statements that may cause an exception.
}
The try block can not detect or handle exception for a block of code that is not defined inside any try block. The try block starts with the try keyword and is embedded between a pair of braces. In a try block, the statements are executed in a normal way until an exception is encountered.
The catch Exception Handler
The catch exception handler follows the try block. The catch exception handler must be placed immediately after the try block. A catch block looks like this.
catch(int err)
{
// C++ error-handling statements
}
The keyword catch can also occur immediately after another catch, that is there can be multiple catch handlers with different parameter lists as:
catch(int err)
{
// C++ error-handling code with int
}
catch(char * msg)
{
// C++ error-handling code with char *
}
The type in the parameter list recognizes the catch handler. Each catch handler would only evaluate an exception that matches or can be converted to the type specified in its parameter list.
The throw Statement
If an exception is detected with in the try block then it is thrown to the appropriate catch handler. C++ provides the throw statement with a data type that causes the exception to be thrown. A throw statement composed of the keyword throw followed by an expression whose type is that of the exception thrown. For example, you can throw a message as:
throw "Encountered an error"
After this, the control is transferred to the catch exception handler that has the char * parameter list. Note that the throw statement cleans up all the objects declared with in try block by calling their destructor first and then it matches the appropriate catch handler passing the parameter object. The general form of a try-catch block is as:
try
{
//….
throw exception;
//….
}
catch(ExceptionType)
{
//….
}
Now let us bring all the three steps try/ throw/catch together.
// Program-lohit2.cpp
#include
#include
#include
class ErrorType
{
};
void Sample();
void main()
{
try
{
Sample();
cout <<"\nBack in try block.";
getch();
}
catch(ErrorType e)
{
cout << "\nCatching an Error.";
}
cout <<"\nThank you.";
}
void Sample()
{
int err=1;
if (err)
{
cout << "\nThrowing an Error.";
throw ErrorType();
}
}
When you run this program you get the following output:
In try block.
Throwing an Error.
Catching an Error.
Exit.
In this program, the Sample() function is placed with in try block. Therefore the Sample() function can throw exceptions and so can any function called by Sample(). In this program, an exception class ErrorType is set up specifically to identify the exception. If any error occur in Sample() function, an exception is thrown by the keyword throw followed by the constructor for the error class as:
throw ErrorType();
After this the control is immediately transferred to the catch block that immediately follows the
try block. Note that the statement
cout<<"\n Back in try block";
is never executed. It happens so because once an exception is thrown the control is transferred to the catch block from try block. Here the catch is not called so there is no provision to return back to the try block from a catch. Once the catch statement has executed, the execution continues with the very first statement in the program following the entire try/ catch mechanism. Here one should remember that when no exception is encountered, the control transfers to the first statement after the catch block. In this case all catch blocks are skipped.
Unfortunately if you throw an exception as:
throw ErrorType;
the compiler would certainly flag an error message. Actually an exception is an object. Therefore the Sample() function must throw an object of class type. You can not throw simply a type as an expression.
In the last program I have create an object of class ErrorType() by calling its constructor as:
throw ErrorType();
This throw statement simply creates an exception of object of type ErrorType.
Multiple Catch Handler
Sometimes a single piece of code may generate more than one exception. To handle this situation, we can specify two or more catch handlers as shown below:
try
{
//….
throw exception;
//….
}
catch(ExceptionType1)
{
//….
}
catch(ExceptionType2)
{
//….
}
……
catch(ExceptionTypen)
{
//….
}
where each catch handler catches a different type of exception. When a function encounters an exception, it is thrown and finally each catch statement is tested in order to match the parameter list with the exception type. The first one whose type matches that of the exception would be executed. After one catch clause executes, the remaining catches are skipped and execution continues after the last catch block.
Following program illustrates this behavior:
// Program-lohit3.cpp
#include
#include
#include
class ErrorType1
{
};
class ErrorType2
{
};
void main()
{
void Sample();
try
{
cout << "\nIn try block.";
Sample();
cout << "\nBack in try block.";
getch();
}
catch(ErrorType1)
{
cout << "\nCatching an Error Type1.";
}
catch(ErrorType2)
{
cout << "\nCatching an Error type2.";
}
cout <<"\nExit.";
}
void Sample()
{
int err=1;
if (err)
{
cout << "\nThrowing an Error type 2.";
throw ErrorType2();
}
}
On execution of this program, we get this output….
In try block.
Throwing an Error type 2.
Catching an Error type2.
Exit.
Catch-All Exception Handler
Since we can not know all the exception that might be thrown and sometime it is not possible for us to provide a specific catch clause for every possible exception. To handle such type of problem C++ provides the facility of catching all uncaught exceptions as:
catch(…)
{
// catch all uncaught exceptions
}
The catch clause has three dots in its parentheses. These three dots are referred to as an ellipsis. Note that this catch-all exception handler must be placed in last where a group of catches associated with a try block.
Following program demonstrates the catch-all handler.
// Program-lohit4.cpp
#include
#include
#include
class ErrorType1
{
};
class ErrorType2
{
};
class ErrorAnyType
{
};
void Sample();
void main()
{
//void Sample();
try
{
cout << "\nIn try block.";
Sample();
cout <<"\n Back in try block.";
getch();
}
catch(ErrorType1)
{
cout << "\n Catching an Error Type1.";
}
catch(ErrorType2)
{
cout << "\nCatching an Error Type2.";
}
catch(ErrorAnyType)
{
cout << "\nCatching an unknown error.";
}
cout <<"\nExit.";
}
void Sample()
{
int err=1;
if (err)
{
cout << "\nThrowing an unknown Error.";
throw ErrorAnyType();
}
}
In this program, the catch-all handler catches the ErrorAnyType exception because none of the other catch handlers has a matching ErrorAnyType parameter list.
Here is the output of this program….
In try block.
Throwing an unknown Error.
Catching an unknown error.
Exit.
Uncaught Exception
Consider a situation where there is no catch handler specified for an exception or one thrown by a constructor that is executing as the result of another throw. When such an exception occurs, it calls build in function terminate() that calls abort() function to terminate the program. Following program illustrates this behavior.
// Program-lohit5.cpp
#include
class ErrorType1
{
};
class ErrorType2
{
};
class ErrorAnyType
{
};
void Sample();
void main()
{
try
{
cout <<"\nIn try block.";
Sample();
cout <<"\nBack in tryblock.";
}
catch(ErrorType1)
{
cout <<"\nCatching an Error Type1.";
}
catch(ErrorType2)
{
cout <<"\nCatching an Error Type2.";
}
}
void Sample()
{
int err=1;
if(err==1)
{
cout <<"\nThrowing an Error.";
throw ErrorAnyType();
}
}
Here is the output….
In try block
Throwing an Error
Abnormal program termination
See the last part of the output
Abnormal program termination.
Actually it is displayed by the abort() function before the termination of the program. However you can also specify a function terminate() to call by calling set_terminate() function, which returns its current value.
// Program-lohit6.cpp
#include
#include
#include
#include
class ErrorType1
{
};
class ErrorType2
{
};
class ErrorAnyType
{
};
void terminator_2()
{
cout << "\nUser Termination.";
exit(-1);
}
void main()
{
void Sample();
set_terminate(&terminator_2);
try
{
cout << "\nIn try block.";
Sample();
cout <<"\nBack in try block.";
getch();
}
catch(ErrorType1)
{
cout << "\nCatching an Error Type1.";
}
catch(ErrorType2)
{
cout << "\nCatching an Error Type2.";
}
}
void Sample()
{
int err=1;
if (err)
{
cout << "\n Throwing an Error.";
throw ErrorAnyType();
}
}
Here is the output….
In try block
Throwing an Error
User Termination
Exception Specification
In earlier programs, the member function Sample() does not guarantee that no other exceptions are thrown by the concerned function. C++ also provides the facility of solving this problem by using an exception specification. In an exception specification, we use a list of exceptions a function may throw with the function declaration. It is the only way that guarantees that the function can not throw any other types of exceptions. An exception specification is specified with the keyword throw followed by a list of exception types enclosed in parentheses. Consider the following function declaration:
void Sample() throw(ErrorType1, ErrorType2)
{
int err=1;
// C++ statements
if (err=1)
{
cout << "\nThrowing Error Type 1.";
throw ErrorType1();
}
if (err==2)
{
cout << "\nThrowing Error Type 2";
throw ErrorType2();
}
}
here
void Sample() throw(ErrorType1, ErrorType2)
is known as exception specification and it means that it can throw two exceptions: ErrorType1 and ErrorType2. It is necessary to include it in the prototype and in the function’s definition header block. Otherwise the compiler reports a type mismatch error when it encounters another declaration of theerror. The declaration of such function is as:
void Sample() throw(ErrorType1, ErrorType2);
Here note that this Sample() function can not throw other types of exception because a function that has throw list is not allowed to throw other types of exceptions. If it throws other type of exception then a runtime error is reported by the compiler. You can also say that an exception specification is a contract between the function and the rest of the program. It is a guarantee that not to throw any exception other than an exception of type listed in its exception specification.
Following program demonstrates this exception specification.
// Program lohit7.cpp
#include
class ErrorType1
{
};
class ErrorType2
{
};
void Sample() throw(ErrorType1,ErrorType2);
void main()
{
try
{
cout<< "\nIn try block.";
Sample();
cout << "\nBack in try block.";
}
catch(ErrorType1)
{
cout << "\nCatching an Error Type1.";
}
catch(ErrorType2)
{
cout << "\nCatching an Error Type2.";
}
}
void Sample() throw(ErrorType1,ErrorType2)
{
int err=1;
if(err==1)
{
cout << "\nThrowing Error Type1.";
throw ErrorType1();
}
if(err==2)
{
cout << "\nThrowing Error type2.";
throw ErrorType2();
}
}
The output of this program is….
In try block
Throwing Error Type 1
Catching an Error Type 1
If a function does not contain a throw list as:
void Sample()
then it means that it can throw any exception. And an empty specification guarantee that the function does not throw any exception. For example, the function declaration
void Sample() throw
guarantees not to throw any kind of exception.
Another main point to mote is that no type conversion is allowed between the type of exception throw and a type specific by the exception specification.
Unexpected Exceptions
If an exception other than the one specified in the exception specification is thrown then a special system function named unexpected() gets called. The default behavior of unexpected() is to call terminate() function. However C++ also provides the facility of overriding the default behavior of unexpected() function. You can also write your own version of unexpected function by using the set_unexpected() function.
Following function catches unexpected exceptions:
// Program-lohit8.cpp
#include
#include
#include
class ErrorType1
{
};
class ErrorType2
{
};
class ErrorAnyType
{
};
void my_unexpected()
{
cout << "\nCatching unexpected exception.";
}
void Sample();
void main()
{
set_unexpected(&my_unexpected);
try
{
cout << "\nIn try block";
Sample();
cout << "\nBack in try block";
}
catch(ErrorType1)
{
cout << "\nCatching an Error Type 1";
}
catch(ErrorType2)
{
cout << "\nCatching an Error Type 2";
}
}
void Sample() throw(ErrorType1, ErrorType2)
{
int err=1;
if (err)
{
cout << "\nThrowing an Error.";
throw ErrorAnyType();
}
}
Here is the output of this program….
In try block
Throwing an Error
Catching unexpected Error
Nested try Blocks
C++ also provides the facility of using nested try blocks. The scenario of nested try blocks looks like this:
try
{
try
{
// Statements
}
catch(ExceptionType)
{
// Statements
}
}
catch(ExceptionType )
{
// Statements
}
Notice that if a particular try block throws an exception and it is not matched by any of the exception handler following that try block then exception moves to the next higher level (the function or try block that surrounds that particular block).
Here is the program that illustrates this behavior.
// Program-lohit9.cpp
#include
#include
class ErrorInnerType
{
};
class ErrorOuterType
{
};
void Sample();
void main()
{
try
{
cout << "\nIn outer try block.";
try
{
cout << "\nIn inner try block.";
Sample();
cout <<"\nBack in outer try block.";
}
catch(ErrorInnerType)
{
cout << "\n Throwing an outer exception from inner try block.";
throw;
}
cout <<"\nBack in outer try block.";
cout << "\nExit.";
}
catch(ErrorOuterType)
{
cout <<"\nCatching an Outer Exception.";
}
}
void Sample()
{
int err=1;
if (err)
{
cout << "\nThrowing an Inner Error.";
throw ErrorInnerType();
}
}
When you run this program you get this output….
In outer try block.
In inner try block.
Throwing an Inner Error.
Catching an inner exception.
Throwing an outer exception from inner try block.
Catching an Outer Exception.
If an exception is thrown with in a particular try block but neither that current level not its higher level contain an appropriate exception handler, then it is the program’s default exception handler that is being called by the compiler.
If you code a throw with no operand in a catch handler or in a function called by a catch handler then it rethrows the original exception. Following program illustrate this behavior.
// Program-lohit10.cpp
#include
#include
#include
void Sample();
class ErrorInnerType
{
};
class ErrorOuterType
{
};
void main()
{
try
{
cout << "\nIn outer try block.";
try
{
cout << "\nIn inner try block.";
Sample();
cout <<"\n Back in outer try block.";
}
catch(ErrorInnerType)
{
cout << "\nCatching an inner exception.";
throw;
}
cout <<"\nBack in outertry block.";
}
catch(ErrorOuterType)
{
cout <<"\nCatching an Outer type error.";
}
catch(ErrorInnerType)
{
cout << "\nCatching an inner exception.";
}
catch(...)
{
cout <<"\nCatching an anytypeof type error.";
}
}
void Sample()
{
cout << "\nThrowing an Inner Error.";
throw ErrorInnerType();
}
The output of this program is as:
In outertry block
In innertry block
Throwing an Inner Error
Catching an inner type error
Catching an outer type error
Back in outertry block
Thank you