PRODUCTS SUPPORT SOLUTIONS SERVICES
COMPAQ SOFTWARE
Compaq C++

Compaq C++
Using Compaq C++ for Tru64 UNIX Systems


Previous Contents Index


Chapter 7
Handling Exceptions

C++ incorporates an exception mechanism for handling unusual program events (not necessarily just errors). Exception handling enables you to detect program events and to provide handlers to deal with the events.

Compaq C++ implements the C++ exception handling model described in Chapter 15 of The Annotated C++ Reference Manual (which is repeated in §r.15 of The C++ Programming Language, 2nd Edition).

This chapter provides a brief introduction to exception handling, describes the run-time considerations of exception handling in Compaq C++, and recommends ways to use exception handlers for optimum performance. For a detailed description of C++ exception handling in general, see Chapter 9 and §r.15 in The C++ Programming Language, 2nd Edition.

Exception handling in Compaq C++ adds no significant overhead to applications that do not use the feature. No performance cost is associated with entering or leaving a try block.

Readers of this chapter should be familiar with the C++ exception-handling terminology, such as throwing and catching exceptions.

For information on debugging programs with exception handlers, see the Tru64 UNIX Ladebug Debugger Manual, which is included with the operating system documentation.

7.1 Structure

The following example shows the basic structure for declaring and using exception handlers in C++:


    .
    .
    .
void might_break(int i) 
{ 
    if (i == 1) throw "something!"; 
    if (i == 4) throw 4; 
    // ...
} 
 
void p (int i) 
{                                //        
    try                          //        begin try block 
    {                            //        
        might_break(i);          //        
    }                            //        
    catch (const char *p)        //        begin handler 
    {                            //         . 
        cout << "caught " << p;  //         . 
        // fix whatever...       //         . 
    }                            //        end try block 
}                                //        end handler 
    .
    .
    .

In this example, calling p with a value of anything other than 1 or 4 causes the program to execute normally, with no exception thrown. If p is called with a value of 1, the might_break function throws a string object to the p handler, which prints caught something! .

If p is called with 4, an int is thrown. Because p cannot catch values of type int , the search for a handler proceeds up the call stack until an appropriate handler can be found. If nothing on the stack can handle an int , program execution terminates immediately after calling the terminate function.

C++ exception handling represents a termination model, which means that program execution never proceeds past a throw . For additional information, see The Annotated C++ Reference Manual.

The following example shows a hierarchy of exceptions to detect and handle:


// program fragment to illustrate exception handling 
 
// Declare the possible exceptions: 
 
class exception {}; 
class failure : public exception {}; 
class process_exception : public exception {}; 
class system_not_running : public process_exception {}; 
class no_privilege : public process_exception {}; 
class no_such_system : public process_exception {}; 
// Add other process exceptions here...
 
// Remote_execute executes a command on a remote system 
void remote_execute ( 
          const char *command, 
          const char *system_name 
          ) throw (process_exception);    // an exception specification 
 
// The following function performs a remote execution, 
// waits indefinitely for a remote system to come up, and 
// prints error messages for other process__exception exceptions. 
 
#include <iostream.h> 
 
void protected_remote_execute ( 
          const char *command, 
          const char *system_name 
          ) throw (failure)          // an exception specification 
{ 
 
    try 
    { 
        for (;;) 
        { 
            try 
            { 
                remote_execute(command, system_name); 
                return; 
            } 
            catch (system_not_running) 
            { 
                // Insert delay code here. 
 
                continue; 
            } 
        } 
    } 
    catch (no_privilege) 
    { 
        cerr << "No privilege to execute process on remote system.\n"; 
    } 
    catch (no_such_system) 
    { 
        cerr << "Remote system does not exist.\n"; 
    } 
    catch (process_exception) 
    { 
        // Catch all process exceptions not dealt with above. 
 
        cerr << "Remote process execution failed.\n"; 
    } 
 
failure f; 
throw f; 
}          
 
    .
    .
    .

In this example, the inner try block detects and handles the system_not_running exception. The outer try block detects and handles the rest of the exceptions from the remote_execute function. When an exception occurs in the outer try block, the handlers are searched for a match in the order listed.

7.2 Run-Time Considerations

Compaq C++ optimizes the implementation of exception handling for normal execution, as follows:

  • Applications that do not use the exception feature incur no overhead.
  • Applications that have handlers and that run without causing exceptions incur only slight overhead as follows:
    • The size of an executable image increases slightly, because of tables that describe the handlers.
    • Code optimization is less aggressive in the presence of handlers.
  • As much as possible, the overhead for exceptions is incurred when throwing an exception. Entering or leaving a try block incurs no overhead.

In Compaq C++, a function with handlers has no intrinsic overhead. For example, functions with handlers do not have frame pointers or do not use additional registers.

Some functions without explicit handlers may have implicit handlers. The compiler creates a handler for each automatic object that has a destructor. The compiler also creates handlers for constructors that initialize subobjects that have destructors. In such a constructor, the compiler creates a handler for each member with a destructor, and a handler for each base class with a destructor. The -nocleanup option suppresses generation of such implicit handlers, which results in a slightly smaller executable file. Use the -nocleanup option for programs that do not use exception handling or do not require destruction of automatic objects during exception processing.

7.3 Coding Recommendations

Some recommendations for optimal results when using exception handling in Compaq C++ are:

  • Use destructors where necessary, but avoid creating them where they are not useful.
    The existence of a destructor can cause the compiler to create an implicit exception handler---as in the example of an implicit exception handler for an automatic object with a destructor.
  • If variables are to be used only within a try block, declare them within the try block.
    The following example declares and uses the variable y outside the try block, and declares and uses the variable x within the try block:


       .
       .
       .
    void foo(); 
    volatile extern int sink; 
     
       .
       .
       .
     
    void func1 (int z) 
    { 
     
        int y = 102; 
     
        sink = y + y; 
     
        try    
        { 
            int x = 88; 
     
            foo(); 
            sink = x + x; 
        } 
        catch (...){    { 
        } 
    } 
       .
       .
       .
    

  • Use fewer and larger try blocks, instead of many small try blocks, wherever possible.
    Whereas a single try block with several handlers may improve performance compared to several small try blocks with one handler each, the improvement may not always be enough to justify making awkward changes to your program logic.

7.4 Mixed-Language Applications

When C functions are intermixed with C++ functions, Compaq C++ treats them as C++ functions without exception handlers.

7.5 Hints on Using Exception Handlers

This section provides hints on using exception handling and elaborates on the coverage of some topics in The C++ Programming Language, 2nd Edition.

7.5.1 Propagating Changes from Exception Handlers

Changes to a caught object are not propagated if the object is caught as a value. For changes to propagate, the object must be caught as a reference. The following example shows how to propagate a change from a handler:


// In the first handler, changes made to the thrown object are not 
// propagated after the throw.  In the third handler, changes made 
// to the thrown object are propagated. 
// (Note that throw with no operand rethrows the current exception.) 
 
extern "C" printf(...); 
 
class A 
{ 
        int i; 
   public: 
        A(int ii = 0) : i(ii) { } 
        void display() { printf("%d\n",i); } 
        void reset() { i = 0; } 
}; 
 
int main () { 
 
    try 
    { 
        try 
        { 
            A a(3);             // create an object 
            throw a;            // throw it 
        } 
        catch (A aa)            // catch 
        { 
            aa.display();       // display current contents (3) 
            aa.reset();         // set contents to 0 
            aa.display();       // display contents after reset (0) 
            throw;              // rethrow -- will not propagate 
        } 
    } 
    catch(A ab) 
    { 
        ab.display();           // display contents -- (3) 
    } 
    try 
    { 
        try 
        { 
            A b(6);             // create an object 
            throw b;            // throw it 
        } 
        catch (A &ba)           // catch as a reference 
        { 
            ba.display();       // display current contents (6) 
            ba.reset();         // set contents to 0 
            ba.display();       // display contents after reset (0) 
            throw;              // rethrow -- will propagate 
        } 
    } 
    catch(A bb) 
    { 
        bb.display();           // display contents -- (0) 
    } 
 
return 0; 
} 

7.5.2 Using the unexpected and terminate Functions

If the unexpected function has an exception specification, Compaq C++ calls it when a function throws an exception that is not specified in the function's definition. You can specify your own unexpected function by calling the set_unexpected function.

On a normal return from an unexpected function, Compaq C++ passes control to the terminate function.

To allow your program to catch an unexpected exception, exit your unexpected function by throwing an exception. The following example shows how to return to a program from an unexpected function:


// This example shows how to return to the normal execution path by 
// exiting my_unexpected() with a throw. 
// 
// Output is: 
// 
//     In my_unexpected(). 
//     Caught HELP. 
// 
// Exit status is 0. 
 
#include "cxx_exception.h" 
 
extern "C" printf(...); 
extern "C" exit(int); 
 
typedef void(*PFV)(); 
PFV set_unexpected(PFV); 
 
void my_unexpected() 
{ 
    printf("In my_unexpected().\n"); 
    throw;                        // Rethrow the current exception. 
} 
 
void foo() throw ()               // Is not allowed to throw anything 
{ 
    throw "HELP";                 // Will result in a call to unexpected(); 
} 
 
int main () 
{ 
 
    // Set unexpected to be my_unexpected 
 
    PFV old_unexpected = set_unexpected(my_unexpected); 
 
    try 
    { 
        foo(); 
    } 
    catch(char * str) { printf("Caught %s.\n",str); } 
    catch(...) { printf("Caught something.\n"); } 
 
    set_unexpected (old_unexpected);           // restore unexpected 
    return 0; 
} 

On a normal return from an unexpected function, Compaq C++ passes control to the terminate function. Although changes made to the unexpected and terminate functions are per thread in higher versions of Compaq C++, take the precaution of restoring the unexpected and terminate functions to their previous values upon all returns from functions that change them using the set_unexpected or set_terminate functions.

7.5.3 Specification Conflicts

Problems can arise if conflicting exception specifications occur in function prototypes and definitions. This section describes how Compaq C++ handles these conflicts. You should avoid conflicting exception specifications, especially in prototypes made available to others in header files.

According to The Annotated C++ Reference Manual, an exception specification is not considered part of a function's type. Therefore, all of the following are legal constructs:

  • A prototype without an exception specification paired with a definition with an exception specification. For example:


    void foo(); 
    void foo() throw(int) {  
     // ...
    } 
    

  • A prototype with an exception specification, paired with a definition without an exception specification. For example:


    void foo() throw(unsigned char); 
    void foo() {  
     // ...
    } 
    

  • A prototype and definition with different exception specifications. For example:


    void foo() throw(int); 
    void foo() throw(float) { 
     // ...
    } 
    

If no guidelines exist regarding such conflicts, Compaq C++ responds as follows:

  • Exception specifications produce compilation errors if they are syntactically incorrect, regardless of whether they appear in a prototype or a definition.
  • In determining the semantics of a function's exception specification, Compaq C++ uses only the specification in the function definition; it ignores the prototype's exception specification.

The following example invokes a call to the unexpected function:


void foo() throw(int);              // prototype exception spec ignored 
void foo() throw() {throw 5;}       // throw 5 illegal after throw() 

The following example does not invoke a call to the unexpected function:


void foo() throw();                 // prototype exception spec ignored 
void foo() throw(int) { throw 5; }  // throw 5 legal after throw(int) 

7.5.4 Using the dlclose Routine

The dlclose routine cannot be used to delete a shared object ( .so ) until after any handlers handling an exception, thrown from the shared object, have exited. This restriction is necessary because, when exiting from a handler, the C++ exception support needs to reference data structures that were defined at the throw point.


Previous Next Contents Index
Buy Online or Call 1.800.888.0220      privacy statement and legal notices  
STORES CONTACT US SEARCH PRODUCTS SOLUTIONS OPTIONS DEVELOPERS