|< C/C++ Storage Classes - auto, extern, static, const etc. | Main | C & C++ Exception Handling 2 >| Site Index | Download |


 

 

 

 

 

MODULE 21

C AND C++ EXCEPTION HANDLING 1

 In the worst case, there must be an emergency exit!

 

 

 

 

 

 

 

 

 

 

 

 

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:

 

  • Able to understand and use C++ exception handlings in general.

  • Able to understand and use the assert() function.

  • Able to understand and use try-throw-catch, structured exception handling (SEH - Microsoft specific).

 

21.1  Introduction

  • When we develop a program, we expect the program does what it is supposed to do without any error.  Many operations, including object instantiation and file processing, are subject to failures that may go beyond errors. Out-of-memory conditions, for instance, can occur even when your program is running correctly.

  • As an example, for typical application program the highest layer may consist of graphic user interface (GUI) part that provide interface for users. These high-level components interact with objects, which in turn encapsulate the application programming interface (API) routines.

  • At a lower level, the API routines may interact with the operating system. The operating system itself invokes system services that deal with low-level hardware resources such as physical memory, file systems, and security modules. In general, runtime errors are detected in these lower code layers should not be handled by themselves.

  • To handle an error appropriately, higher-level components have to be informed that an error has occurred. Generally, error handling consists of detecting an error and notifying the components that are in charge. These components in turn attempt to recover from the error or terminate the program properly.

  • From the simplest one, we may use a proper prompting, for example:

Enter two integer separated by space:

  • But what about if the user enter other than integer and not separate it by space?  At least there must be a prompt message or an alert dialog box, if the invalid data entered such as classic messages Abort, Retry, Ignore where:

 

Message

Description

Retry

Debug the assertion or get help on asserts.

Ignore

Ignore the assertion and continue running the program.

Abort

Halt execution of the program and end the debugging session.

 

Table 21.1: Abort, Retry and Ignore

 

21.2  Assertions

// C++ and standard C

#include <assert.h>

// #include <cassert>

// assertion macro

assert(expression);

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);

 

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

// turn assertion checking off

#define NDEBUG

                       

// undefined the NDEBUG, turn on the assertion if

// #defined DEBUG has been defined...

#undef NDEBUG

  1. 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.

  2. 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.

  3. 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.

21.3  C Exception - structured exception handling (SEH - Microsoft® implementation)

// 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...

       }

}

 

Output:

 

Microsoft C exception handling

21.4  C++ Exception

try

{

       buff = new char[1024];

       if(buff == 0)

              throw "Memory allocation failure!";

}

 

// catch what is thrown...

catch(char* strg)

{

       cout<<"Exception raised: "<<strg<<endl;

}

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

}

21.4.1  try

21.4.2  throw

21.4.3  catch

  1. The keyword catch.

  2. A catch parameter, enclosed in parentheses (), which corresponds to a specific type of exception that may be thrown by the try block.

  3. A group of statements, enclosed in curly braces { }, whose purpose is to handle the exception.

catch(...)

  • Then, the catch clause will handle any type of exception, including C exceptions and system or application generated exceptions such as divide by zero, memory protection and floating-point violations. Such a handler must be the last handler for its try block acting as default catch.

  • The catch handlers are examined in order of their appearance following the try block. If no appropriate handler is found, the next dynamically enclosing try block is examined. This process continues until the outermost enclosing try block is examined if there are more than one try block.

  • If a matching handler is still not found, or if an exception occurs while unwinding, but before the handler gets control, the predefined run-time function terminate() is called. If an exception occurs after throwing the exception, but before the unwinding begins, terminate() is also called.

  • The catch block must go right after the try block without any line of codes between them.

  • The order in which catch handlers appear is important, because handlers for a given try block are examined in order of their appearance.  For example, it is an error to place the handler for a base class before the handler for a derived class.

  • After a matching catch handler is found, subsequent handlers are not examined.  That is why an ellipsis catch, catch(...) handler must be the last handler for its try block.

  • Besides that, catch may be overloaded so that it can accept different types as parameters. In that case the catch block executed is the one that matches the type of the exception sent through the parameter of throw.

  • A program example:

 

// 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;

}

 

Output:

 

a very simple try-throw-catch program example output

// 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;

}

 

Output:

 

try-throw-catch program example with multiple catch

 

21.5  Catching Exceptions

catch(Type test)
catch(const Type test)
catch(Type & test)
catch(const Type& test)
  1. Type and Type1 are the same type, or

  2. Type is an accessible base class of Type1 at the throw point, or

  3. 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.

  1. Type is the same type as Type1, except it may have added any or both of the qualifiers const and volatile, or

  2. Type is void*, or

  3. 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).

// 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;      

}

 

Output:

 

C++ exception handling try-throw-catch with mismatch type

 

throw 3;           to            throw 4.123;

C++ exception handling try-throw-catch with a proper and match type

  1. A handler that can accept any type (using the ellipsis syntax).

  2. A handler that accepts the same type as the exception object; because it is a copy, const and volatile modifiers are ignored.

  3. A handler that accepts a reference to the same type as the exception object.

  4. A handler that accepts a reference to a const or volatile form of the same type as the exception object.

  5. 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.

  6. A handler that accepts a reference to a base class of the same type as the exception object.

  7. A handler that accepts a reference to a const or volatile form of a base class of the same type as the exception object.

  8. A handler that accepts a pointer to which a thrown pointer object can be converted via standard pointer conversion rules.

// 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;

}

 

Output:

 

 

 

C++ exception handling try-throw-catch - an exception, class, constructor and destructor

 

 

 

21.6  Exception Processing-Stack Unwinding

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;

}

try

{

       throw SomeException();

}

 

// handle all exceptions

catch(...)

       // respond (perhaps only partially) to exception

       // ...

       // re-throw without operand...

       // pass exception to some other handler

       throw;

}

// empty throw statement

#include <iostream>

using namespace std;

 

// this empty throw will be ignored...

void Nothing() throw()

{ cout<<"In Nothing(), empty throw..."<<endl; }

 

void SomeType() throw(double)

{

       cout<<"In SomeType, will throw a double type..."<<endl;

       throw(1.234);

}

 

void main()

{

       try

       {

              Nothing();

              SomeType();

       }

       catch (double)

       { cout<<"Caught a double..."<<endl; }

}

 

Output:

 

C++ exception handling try-throw-catch - an empty throw

 

21.7  Uncaught/Unhandled Exception

#include <iostream>

using namespace std;

// a base class

class Test1 { };

// a derived class

class Test2 : public Test1 { };

    

void Funct();

    

int main()

{

       try

       {

              // a function call, go to Funct()

              Funct();

       }

       catch(const Test1&)

       {

              cerr<<"Caught a Test1 type, base class..."<<endl;

       }

       return 0;

}

    

// throw function definition, a throw of Test2 type, derived class...

void Funct()

{

       throw Test2();

       // next, find the catch handler

}

 

Output:

 

C++ exception handling try uncaught/unhandled throw

#include <iostream>

using namespace std;

// for exit()

#include <cstdlib>

// for set_terminate()

#include <exception>

 

void Funct()

{

       cout<<"Funct() was called by terminate()."<<endl;

       // 0-normal exit, non zero-exit with some error

       exit(0);

}

 

int main()

{

       try

       {

              set_terminate(Funct);

              // no catch handler for this exception

              throw "Out of memory!";

       }

       catch(int)

       { cout<<"Integer exception raised."<<endl; }

   return 0;

}

 

Output:

 

C++ exception handling try-throw-catch using the terminate() to terminate the try block

 

tenouk fundamental of C++ exception handling

 

 

 

 

 

 

 

 

 

 

 

Further C/C++ exception handling related reading:

 

  1. The source code for this tutorial is available in C/C++ Exception Handling source code.

  2. Check the best selling C / C++, Object Oriented and pattern analysis books at Amazon.com.

 

 

 

 

 

 

|< C/C++ Storage Classes - auto, extern, static, const etc. | Main | C & C++ Exception Handling 2 >| Site Index | Download |


C & C++ Exception Handling:  Part 1 | Part 2