C++ Exception Examples
The real power of C++ exception handling lies not only in its ability to deal with exceptions of varying types, but also in its ability to automatically call destructor functions during stack unwinding for all local objects constructed before the exception was thrown.
The context which exists between the throw site and the catch handler is referred to as the “exception stack frame.” This frame may contain objects with destructor semantics. If an exception is thrown during execution of the guarded section or in any routine the guarded section calls (directly or indirectly), an exception object is created from the object created by the throw operand. (This implies that a copy constructor may be involved.) 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 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 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 unwind begins, the terminate function is called. You can install a custom termination function to handle such situations. See Unhandled Exceptions for more information.
The following example demonstrates C++ exception handling using classes with destructor semantics. It declares two C++ classes; one (class CTest
) for defining the exception object itself, and the second (class CDtorDemo
) for demonstrating the destruction of a separate frame object during stack unwinding:
#include <iostream.h>
void MyFunc( void );
class CTest
{
public:
CTest(){};
~CTest(){};
const char *ShowReason() const { return "Exception in CTest class."; }
};
class CDtorDemo
{
public:
CDtorDemo();
~CDtorDemo();
};
CDtorDemo::CDtorDemo()
{
cout << "Constructing CDtorDemo." << endl;
}
CDtorDemo::~CDtorDemo()
{
cout << "Destructing CDtorDemo." << endl;
}
void MyFunc()
{
CDtorDemo D;
cout<< "In MyFunc(). Throwing CTest exception." << endl;
throw CTest();
}
int main()
{
cout << "In main." << endl;
try
{
cout << "In try block, calling MyFunc()." << endl;
MyFunc();
}
catch( CTest E )
{
cout << "In catch handler." << endl;
cout << "Caught CTest exception type: ";
cout << E.ShowReason() << endl;
}
catch( char *str )
{
cout << "Caught some other exception: " << str << endl;
}
cout << "Back in main. Execution resumes here." << endl;
return 0;
}
If a matching catch handler is found, and it catches by value, its formal parameter is initialized by copying the exception object. If it catches by reference, the parameter is initialized to refer to the exception object. After the formal parameter is initialized, the process of “unwinding the stack” begins. This involves the destruction of all automatic objects that were constructed (but not yet destructed) between the beginning of the try block associated with the catch handler and the exception’s throw site. Destruction occurs in reverse order of construction. The catch handler is executed and the program resumes execution following the last handler (that is, the first statement or construct that is not a catch handler).
This is the output from the preceding example:
In main.
In try block, calling MyFunc().
Constructing CDtorDemo.
In MyFunc(). Throwing CTest exception.
Destructing CDtorDemo.
In catch handler.
Caught CTest exception type: Exception in CTest class.
Back in main. Execution resumes here.
Note the declaration of the exception parameter in both catch handlers:
catch( CTest E )
{ // ... }
catch( char *str )
{ // ... }
You do not need to declare this parameter; in many cases it may be sufficient to notify the handler that a particular type of exception has occurred. However, if you do not declare an exception object in the exception declaration, you will not have access to the object in the catch handler clause. For example:
catch( CTest )
{
// No access to a CTest exception object in this handler.
}
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:
try
{
throw CSomeOtherException();
}
catch(...) // Handle all exceptions
{
// Respond (perhaps only partially) to exception
//...
throw; // Pass exception to some other handler
}