Exception Handling
What happens when there is an runtime error in the program like array index out of range or division by zero? The program crashes, the data will not be saved and the user will not have any clue about what happened.
What if we could anticipate such error conditions and if and when there is an error, gracefully exit after closing files, saving information etc? That is the precise purpose of exception handling.
Exception handling allows you to anticipate and manage run time errors in an orderly fashion. Using exception handling, your program can automatically invoke error handling code when there is a run time error.
Exception handling also separates program logic and error handling logic there by making the program easier to write and manage.
In C++, exception handling is performed with the help of try and catch statements.
try and catch
Exception handling code is written using try and catch blocks.
-
The code which may cause an error is enclosed in a try block.
-
Try block may throw an exception implicitly or explicitly.
-
A try block is followed by one or more catch blocks.
-
Catch blocks contain the code to handle error.
-
If there are no errors, the execution will ignore catch blocks and continue normally
-
If there is an error and an exception is thrown in try block, the subsequent statements are skipped and a catch block with exception type which matches the thrown exception is executed.
The thrown exception can be of POD type or a structure or class.
syntax
try
{
/*code which may produce error
*/
}
catch(type identifier)
{
/*statements*/
}
catch(type identifier)
{
/*statements*/
}
Let us look at an example.
Here, if m2 is 0, then the program throws 0 - an integer exception. So the next statements are skipped and program continues with catch blocks. Since the there is a catch block with integer error type, that block is executed which just prints Denominator is zero.
What if m2 is not zero? In that case m1/m2 is printed and catch block is ignored.
./a.out
10 0
Denominator is 0
./a.out
10 2
5
In the program above, division code is enclosed in a try block.
When m2 is 0, the program throws an int exception 0. Throw can throw an int/float etc (POD) type exceptions or objects.
A try block must always be followed by one or more catch blocks.
A catch block looks like a function with single parameter - with its type and name. If the thrown exception object is of the same type as catch block parameter, that catch block is executed and then program continues normally. If not, the next catch block is searched for.
In the program above, there is an int catch block (int is data type, e is exception variable) which matches the thrown error object. So the code inside catch (int e ) block is executed and program continues after that.
What if none of the catch blocks match with thrown object?
If there is no catch block with same type as thrown object, the program calls terminate() function and ends.
Let us modify the earlier program and provide only a float catch handler.
Now if we execute the program, this is what happens.
./a.out
10 0
terminate called after throwing an instance of 'int'
Aborted
Here when denominator is 0, an integer error is thrown. But there is no catch block with int data type. So program crashes.
Exact Match:
Type specified by catch clause should exactly match with thrown type because implicit type conversions does not happen in exception handling.
e.g. if an exception of type unsigned int is thrown, it is not caught by "int" catch block.
Base and Derived class Exception Objects
Let us look at another example.
When we compile the above program we see warnings. Let us choose to ignore them and execute the program.
./a.out
10 0
ExBase object is caught
What went wrong? We certainly threw an object of ExDerived class in try block. Why did the program catch ExBase class?
The reason is - when there are multiple matching catch blocks, the exception is caught by the first matching catch block.
In this case since ExDerived is an object of ExBase (A derived class object is a base class object), the base class catch block handles the exception.
In such situations, write derived class handler earlier and base class handler later.
Catch all - ellipsis
A catch block with ellipsis (catch(...) ) catches all exceptions. If used, this catch block should be the last one.
The last catch block, catches all exceptions.
In this example, if we write catch all in the beginning, we will not get errors for negative index or index exceeding size of the array.
Stack Unwinding
C++ maintains a stack of function calls. If a function throws an exception and but does not handle it, the function is popped out of stack and next function in the stack is continued and the corresponding catch block is searched in this function. If not found, this too is popped out of the stack. This process continues until a matching catch handler is found. If no such a handler is found, then the program crashes by calling the terminate function.
This process is called stack unwinding. When stack is unwinding, all local objects are destroyed by calling their destructors.
Note: The destructors of all local objects are called only if the exception is handled.
Here find_quotient() throws an exception of type DivEx. Since the function does not handle it, it is passed on to the next function on stack which is calculate(). In the mean time, temp2 is cleaned up using its destructor. Now the handler is searched in calculate function. It is not found there. The function is unwound and destructor of temp1 is called and execution enters main() which handles the exception.
Exception specification
A function can specify which unhandled exceptions it may throw outside using exception specifiers. This helps the users to handle the specified exceptions in their code.
Exception specifier for a function is written in function header with keyword throw followed by comma separated list of exception types within parentheses.
If a function throws an exception not given in specifier , then the program crashes.
void divideNums(int m,int n) throw (int);
Here divideNums() may throw only int exception.
If divideNums() throws an unhandled int exception, there will not be any problem. But if let us say, this function throws a float exception and does not handle it, then the program crashes.
getElement() function throws an integer error if index is negative and it throws a float error if the index is >=size of the array. And it does not handle the exceptions.
Since it can throw only int and float unhandled exceptions exception specification is (int, float). So it is good.
Next let us modify the getElement() function.
If we run the program and give index value greater than 5, then getElement() throws a float error. But float is not specified in exception specifier. So the program crashes.
./a.out
10
terminate called after throwing an instance of 'float'
Aborted
Empty parenthesis as exception specifier tells us that the function can not throw out any unhandled exceptions.
void sumOfNums(int a, int b) throw()
sumOfNums() can not throw any exception.
A function which does not specify throw clause in its header may throw any type of exception.
void foo ();
foo() can throw any type of exception.
Rethrowing An Exception
From inside a handler, an exception can be thrown again for further processing using empty throw statement.
Here double exception is caught and thrown again within catch block. The calling function can catch this again and handle it.