|
| My Training Period: xx hours
The compiler used to compile the program examples in this Module is Visual Studio 6.0®, an empty Win32 Console Mode application. There is no C++ standard <exception> class found in my Borland® C++ 5.02 compiler. Check your compiler documentation and don’t forget to install any patches and Service Packs if any. For Borland you may try Borland C++ Builder 5.5, 6.0 or multiplatform C++ BuilderX. Examples also tested using VC++ .Net. The source code for this tutorial is available in C/C++ Exception Handling source code. I found that exception handling also used intensively in C++ .NET programming.
The C & C++ programming skills that supposed to be acquired in this session:
21.1 Introduction
Enter two integer separated by space:
| ||||||||||
Throughout this tutorial also, you should have encountered several mechanism used, such as conditional statements using the if statements combined with exit(), abort() and terminate() functions, when there are errors, the program just terminate with some error message, passing the control back to operating system. Some of the messages may be very useful for our debugging process.
We also have had used the assert() function to test the validity of the program expressions as discussed in C/C++ Preprocessor Directives.
But program logic can’t be proved correct under all situation, we must ready for this situation by providing the emergency exit for them.
C++ provides two methods to handle this anomalous situation called exceptions, that are using assertions and exceptions.
Assertion has been discussed in C/C++ Preprocessor Directives and it should be a revision here.
Same as C, C++ also supports assertion programming. Programmer specifies checks for correct conditions to continue program execution. We use assert.h library for standard C and <cassert> for C++ (C++ wrapper), something like this:
// C++ and standard C
#include <assert.h>
// #include <cassert>
Preprocessor macro assert() used to provide assertion processing. Macro expects an expression with an integral value, for example:
// assertion macro
assert(expression);
A code snippet example:
cout<<"Enter an integer: "<<endl;
cin>>p;
if(p!=0)
cout<<"p x p x p = "<<p*p*p<<endl;
else
// 0 - normal exit, non-zero-some error
exit(1);
From the code snippet, we know that:
| If (p!=0) | The program will continue normally. |
| If (p==0) | The assertion fails, error message displayed in the following form and program terminates. Assertion failed: expression, file filename, line number |
Assertion processing can be disabled by enabling the NDEBUG (no debug):
// turn assertion checking off
#define NDEBUG
// undefined the NDEBUG, turn on the assertion if
// #defined DEBUG has been defined...
#undef NDEBUG
Assertion processing typically used only during program development and debugging. The assert expression is not evaluated in the Release Version of your program. Typically, assertions can be used for:
Catching the program logic errors. Use assertion statements to catch logic errors. You can set an assertion on a condition that must be true according to your program logic. The assertion only has an effect if a logic error occurs.
Checking the results of an operation. Use assertion statements to check the result of an operation. Assertions are most valuable for testing operations which results are not so obvious from a quick visual inspection.
Testing the error conditions that supposed to be handled. Use assertions to test for error conditions at a point in your code where errors supposed to be handled.
This part presented here just as a comparison and discussion to the standard C++.
A structured exception handler has no concept of objects or typed exceptions, it cannot handle exceptions thrown by C++ code; but, C++ catch handlers can handle C exceptions.
So, the C++ exception handling syntax using try, throw…catch is not accepted by the C compiler, but structured exception handling syntax (Microsoft® implementation) using __try, __except, __finally is supported by the C++ compiler.
The major difference between structured exception handling and C++ exception handling is that the C++ exception handling deals with types, while the C structured exception handling deals with exceptions of one type specifically, unsigned int.
C exceptions are identified by an unsigned integer value, whereas C++ exceptions are identified by data type.
When an exception is raised in C, each possible handler executes a filter that examines the C exception context and determines whether to accept the exception, pass it to some other handler, or ignore it whereas when an exception is thrown in C++, it may be of any type.
C structured exception handling model is referred to as what is called asynchronous, which the exceptions occur secondary to the normal flow of control whereas the C++ exception handling mechanism is fully synchronous, which means that exceptions occur only when they are invoked or thrown.
If a C exception is raised in a C++ program, it can be handled by a structured exception handler with its associated filter or by a C++ catch handler, whichever is dynamically closer to the exception context.
The following is a program example of the C++ program raises a C exception inside a C++ try block:
// C structured exception handling and C++ exception handling
#include <iostream>
using namespace std;
// function prototype...
void TestCFunct(void);
int main()
{
// C++ try block...
try
{
// a function call...
TestCFunct();
}
// a catch block...
catch(...)
{
cout<<"Caught the exception, C style..."<< endl;
}
return 0;
}
// a function definition...
void TestCFunct()
{
// structured handling exception...
__try
{
int p, r = 2, q = 0;
// exception should be raised here divide by 0...
p = r*(10/q);
}
__finally
{
cout<<"In __finally" << endl;
// finding the appropriate catch...
}
}

Besides that, C’s exception that uses the setjmp() and longjmp() functions do not support C++ object semantics. Using these functions in C++ programs may lesser the performance by preventing optimization on local variables. It is better to use the C++ exception handling try-throw-catch constructs instead.
21.4 C++ Exception
An exception occurs when an unexpected error or unpredictable behaviors happened on your program not caused by the operating system itself. These exceptions are handled by code which is outside the normal flow of control and it needs an emergency exit.
Compared to the structured exception handling, returning an integer as an error flag is problematic when dealing with objects. The C++ exception-handling can be a full-fledged object, with data members and member functions.
Such an object can provide the exception handler with more options for recovery. A clever exception object, for example, can have a member function that returns a detailed verbal description of the error, instead of letting the handler look it up in a table or a file.
C++ has incorporated three operators to help us handle these situations: try, throw and catch.
You may find that this try-throw-catch exception handling is also used widely in C++ .NET programming.
The following is the try-throw-catch program segment example:
try
{
buff = new char[1024];
if(buff == 0)
throw "Memory allocation failure!";
}
// catch what is thrown...
catch(char* strg)
{
cout<<"Exception raised: "<<strg<<endl;
}
In grammar form:
The try-block:
try
{compound-statement handler-list
handler-list here
The throw-expression:
throw expression
}
{
The handler:
catch (exception-declaration) compound-statement
exception-declaration:
type-specifier-list here
}
Let discuss in detail one by one.
A try block is a group of C++ statements, enclosed in curly braces { }, that might cause an exception. This grouping restricts the exception handlers to the exceptions generated within the try block. Each try block may have one or more associated catch blocks.
If no exception is thrown during execution of the guarded section, the catch clauses that follow the try block are not executed or bypassed. Execution continues at the statement after the last catch clause following the try block in which the exception was thrown.
If an exception is thrown during execution of the guarded section or in any routine the guarded section calls either directly or indirectly such as functions, an exception object will be created from the object created by the throw operand.
At this point, the compiler looks for a catch clause in a higher execution context that can handle an exception of the type thrown or a catch handler that can handle any type of exception. The compound-statement after the try keyword is the guarded section of code.
The throw statement is used to throw an exception and its value to a matching catch exception handler. A regular throw consists of the keyword throw and an expression. The result type of the expression determines which catch block receives control.
Within a catch block, the current exception and value may be re-thrown simply by specifying the throw keyword alone that is without the expression.
The throw is syntactically similar to the operand of a return statement but here, it returns to the catch handler.
A catch block is a group of C++ statements that are used to handle a specific thrown exception. One or more catch blocks, or handlers, should be placed after each try block. A catch block is specified by:
The keyword catch.
A catch parameter, enclosed in parentheses (), which corresponds to a specific type of exception that may be thrown by the try block.
A group of statements, enclosed in curly braces { }, whose purpose is to handle the exception.
The compound-statement after the catch keyword is the exception handler, and catches or handles the exception thrown by the throw-expression.
The exception-declaration statement part indicates the type of exception the clause handles. The type can be any valid data type, including a C++ class.
If the exception-declaration statement part is just an ellipsis (...) such as,
catch(...)
|
// a very simple try-throw-catch example
#include <iostream>
using namespace std;
int main()
{
// declare char pointer
char* buff;
// try block...
try
{
// allocate storage for char object...
buff = new char[1024];
// do a test, if allocation fails...
if(buff == 0)
throw "Memory allocation failure!";
// if allocation successful, display
// the following message, bypass the catch block...
else
cout<<sizeof(buff)<<" Byte successfully allocated!"<<endl;
}
// if allocation fails, catch the type, display a message...
catch(char* strg)
{
cout<<"Exception raised: "<<strg<<endl;
}
return 0;
}

A program example for multiple catch:
// exception: multiple catch blocks
#include <iostream>
using namespace std;
int main ()
{
try
{
char * teststr;
teststr = new char [10];
// test, if memory allocation fails then,
// throws this error to the matching catch...
if (teststr == NULL) throw "Allocation failure";
for (int i=0; i<=15; i++)
{
// another test, if n>9, throw this error, to the respective catch..
if (i>9) throw i;
teststr[i]='z';
cout<<"teststr["<<i<<"] = "<<teststr[i]<<endl;
}
}
// catch the error if i > 9, by displaying some error message...
catch (int j)
{
cout<<"The exception: ";
cout<<"index "<<j<<" is out of range"<<endl;
}
// catch the error if, allocation fail for *teststr, by displaying some error...
catch (char * strg)
{
cout<<"The exception: "<<strg<<endl;
}
return 0;
}

Since exceptions are a run-time and not a compile-time feature, standard C++ specifies the rules for matching exceptions to catch-parameters is slightly different from those for finding an overloaded function to match a function call.
We can define a handler for an object of type named Type several different ways. In the following examples, the variable test is optional, just as the ordinary functions in C++:
catch(Type test) catch(const Type test) catch(Type & test) catch(const Type& test) Such handlers can catch exception objects of type Type1 if:
Type and Type1 are the same type, or
Type is an accessible base class of Type1 at the throw point, or
Type and Type1 are pointer types and there exists a standard pointer conversion from Type1 to Type at the throw point. Type is an accessible base class of Type1 if there is an inheritance path from Type1 to Type with all public derivations.
For the third rule, let Type1 be a type pointing to type Type2, and Type be a type that points to type Type3. Then there exists a standard pointer conversion from Type1 to Type if:
Type is the same type as Type1, except it may have added any or both of the qualifiers const and volatile, or
Type is void*, or
Type3 is an unambiguous, accessible base class of Type2. Type3 is an unambiguous base class of Type2 if Type2's members can refer to members of Type3 without ambiguity (this is usually only a concern with multiple inheritance).
The C++ type conversion is discussed in Typecasting.
As conclusion, for these rules, the exceptions and catch parameters must either match exactly, or the exception caught by pointer or reference must be derived from the type of the catch parameter.
For example, the following exception is not caught:
// mismatch type, throw an integer type but catch the double type...
#include <iostream>
using namespace std;
// a prototype
void Funct();
int main()
{
try
{ Funct(); }
catch(double)
{ cerr<<"caught a double type..."<<endl; }
return 0;
}
void Funct()
{
// 3 is not a double but int
throw 3;
}

Change the following statement
throw 3; to throw 4.123;
Re-compile and re-run, the program output should be as follows:

As a summary, when an exception is thrown, it may be caught by the following types of catch handlers:
A handler that can accept any type (using the ellipsis syntax).
A handler that accepts the same type as the exception object; because it is a copy, const and volatile modifiers are ignored.
A handler that accepts a reference to the same type as the exception object.
A handler that accepts a reference to a const or volatile form of the same type as the exception object.
A handler that accepts a base class of the same type as the exception object; since it is a copy, const and volatile modifiers are ignored. The catch handler for a base class must not precede the catch handler for the derived class.
A handler that accepts a reference to a base class of the same type as the exception object.
A handler that accepts a reference to a const or volatile form of a base class of the same type as the exception object.
A handler that accepts a pointer to which a thrown pointer object can be converted via standard pointer conversion rules.
C++ exception is automatically call destructor functions during the stack unwinding process, for all local objects constructed before the exception was thrown.
A program example.
// exception, class and destructor
#include <iostream>
using namespace std;
void TestFunct(void);
// class Test1 declaration...
class Test1
{
public:
Test1(){ };
~Test1(){ };
const char *TestShow() const
{
cout<<"In class member function *TestShow():\n";
return " Exception in Test1 class.";
}
};
// another class declaration, DestrTest...
class DestrTest
{
public:
DestrTest();
~DestrTest();
};
// constructor class implementation
DestrTest::DestrTest()
{
cout<<"Next, in constructor DestrTest():\n";
cout<<" Constructing the DestrTest...\n";
}
// destructor class implementation
DestrTest::~DestrTest()
{
cout<<"Next, in destructor ~DestrTest():\n";
cout<<" Destructing the DestrTest...\n";
}
void TestFunct()
{
// instantiate an object, constructor invoked...
DestrTest p;
cout<<"Next in TestFunct(): \n Throwing Test1 type exception...\n";
// first throw...
throw Test1();
}
int main()
{
cout<<"Starting in main()...\n";
try
{
cout<<"Now, in the try block: \n Calling TestFunct()...\n";
TestFunct();
}
// instantiate another object, constructor invoked...
catch(Test1 q)
{
cout<<"Next, in catch handler:\n";
cout<<" Caught Test1 type exception...\n";
cout<<q.TestShow()<<"\n";
}
catch(char *strg)
{
cout<<"Caught char pointer type exception: "<<strg<<"\n";
}
cout<<"Back in main...\n";
return 0;
}

When an exception is thrown, the runtime mechanism first searches for an appropriate matching handler (catch) in the current scope. If no such handler exists, control is transferred from the current scope to a higher block in the calling chain or in outward manner.
Iteratively, it continues until an appropriate handler has been found. At this point, the stack has been unwound and all the local objects that were constructed on the path from a try block to a throw expression have been destroyed.
The run-time environment invokes destructors for all automatic objects constructed after execution entered the try block. This process of destroying automatic variables on the way to an exception handler is called stack unwinding.
During the unwinding the stack , objects on stack are destroyed, local variables, local class objects destructors are called and program goes back to a normal state.
The stack unwinding process is very similar to a sequence of return statements, each returning the same object to its caller.
In the absence of an appropriate handler, the program terminates. However, C++ ensures proper destruction of local objects only when the thrown exception is handled. Whether an uncaught exception causes the destruction of local objects during stack unwinding is implementation-dependent.
To ensure that destructors of local objects are invoked in the case of an uncaught exception, you can add a catch(...) statement in main(). For example:
int main()
{
try
{
// throw exceptions...
throw SomeThing;
}
// handle expected exceptions
catch(TheSomething)
{
// handle all the exceptions...
}
// ensure proper cleanup in the case
// of an uncaught exception
catch(...)
{
// catch other things…
}
return 0;
}
A throw expression with no operand re-throws the exception currently being handled. Such an expression should appear only in a catch handler or in a function called from within a catch handler. The re-thrown exception object is the original exception object (not a copy). For example:
An empty throw statement tells the compiler that the function does not throw any exceptions. For example:

When the system can't find a handler for an exception, it calls the standard library function terminate(), which by default aborts the program. You can substitute your own termination function by passing a pointer to it as a parameter to the set_terminate() library function.
An exception caught by a pointer can also be caught by a void* handler. In the following program example, exception is caught, since there is a handler for an accessible base class:
using namespace std;

Another program example for terminating the try block:
using namespace std;

tenouk fundamental of C++ exception handling
The source code for this tutorial is available in C/C++ Exception Handling source code.
Check the best selling C / C++, Object Oriented and pattern analysis books at Amazon.com.