C++ API User Guide

Chapter 6

Exception Handling

ObjectStore does not support the use of the C++ exception handling for error conditions that occur during an ObjectStore operation. Instead, ObjectStore provides the TIX exception-handling facility, which includes an error-reporting mechanism and services for handling ObjectStore exceptions.

This chapter discusses the following topics:

Note
For detailed descriptions of all components of the TIX exception-handling facility, see the following chapters in the C++ API Reference:

TIX Exceptions

TIX exceptions are part of the API that ObjectStore uses to report and handle error conditions during an ObjectStore operation. These exceptions include system-supplied, predefined exceptions, as well as any defined by the user. The following sections describe both predefined and user-defined exceptions.

Predefined Exceptions

The predefined TIX exceptions are instances of the class tix_exception. These exceptions are used to signal illegal ObjectStore operations, such as attempting to write to persistent memory during a read-only transaction. The predefined exceptions are derived from objectstore_exception and have access to its member functions, including the signal() method, which is described in Signaling Exceptions. For a descriptive list of the predefined TIX exceptions, see Chapter 5, Predefined TIX Exceptions, of the C++ API Reference.

The predefined exceptions are organized into groups. Each group has a parent exception; the exceptions within the group are referred to as child exceptions. Parent exceptions can be used where you would use child exceptions, but they are most useful when specified in a TIX exception handler to catch a group of child exceptions; see Handling Multiple Exceptions.

Parent exceptions are organized hierarchically, as illustrated in the following diagram. Note the following relationships among the parent exceptions:



User-Defined Exceptions

TIX exceptions that you define are instances of the class objectstore_exception, which provides functions for signaling the exception; see Signaling Exceptions. ObjectStore provides two macros for defining your own TIX exceptions:

DEFINE_EXCEPTION(name, "message", pointer-to-parent ); 
DECLARE_EXCEPTION(name ); 
The DEFINE_EXCEPTION macro performs the definition and the DECLARE_EXCEPTION macro makes the definition available to other program files. Both macros must end in a semicolon (;).

Arguments
name is the identifier for the exception you want to create.

message is the error message to display when the exception is signaled. As shown in the syntax statement, this argument must be enclosed in quotation marks.

pointer-to-parent is the address of a parent exception, which thus becomes the new exception's parent. For information about parent exceptions, see Predefined Exceptions. Supply 0 for this argument if you do not want your exception to have a parent.

Defining and declaring
If you use the exception in only one file, you can dispense with the DECLARE_EXCEPTION macro altogether and just include the DEFINE_EXCEPTION macro in the program file that uses the exception. Otherwise, include DECLARE_EXCEPTION in the definitions file, and DEFINE_EXCEPTION in a header file that is included by program files that want to use the exception.

Signaling
Predefined TIX exceptions are signaled internally by ObjectStore. How do you signal a user-defined exception? It is triggered explicitly by the user application when it detects the condition for which the exception was defined. User-defined (as well as predefined) exceptions are instances of the class objectstore_exception, which gives them access to the function objectstore_exception::signal(). When you call this function, specify the exception identifier as the implicit this argument; the effect is to signal the exception.

For reference information about signal(), see objectstore_exception::signal() in the C++ API Reference.

Example
The example program discussed in Chapter 2, Anatomy of an ObjectStore Application shows defining, declaring, and using a user-defined TIX exception. Following is a summary of the process.

The exception is defined in the definitions file, bookclub.cc.

DEFINE_EXCEPTION(err_no_entry_point, 
      "No entry-point object for", 0); 
To make the exception err_no_entry_point available to other parts of the program, it is declared in the header file, bookclub.hh, line 10:

DECLARE_EXCEPTION(err_no_entry_point); 
One of the program files that includes bookclub.hh is the main application file, main.cc, which also uses the exception. In lines 38-39, an if statement verifies that the return value from get_value() was nonzero, indicating that the entry-point object was retrieved; see Retrieving the Entry-Point Object. If get_value() returns 0 (no entry-point object was retrieved), the program calls signal(), as follows:

err_no_entry_point.signal( 
      "%s - use os_database_root::set_value()", argv[1]); 
This call to signal() triggers the exception. signal() also performs printf-like formatting for displaying additional information about the error condition; see Signaling Exceptions.

In this example, the exception is not handled and the program aborts. However, you can handle user-defined as well as predefined exceptions in a TIX exception handler, as described in Establishing a TIX Exception Handler.

Establishing a TIX Exception Handler

ObjectStore provides four macros for establishing TIX exception handlers:

TIX_HANDLE (exception) 
TIX_HANDLE_IF (flag, exception) 
TIX_EXCEPTION 
TIX_END_HANDLE 
exception is the name of the exception you want to handle. It can be one of the following:

The only difference between TIX_HANDLE and TIX_HANDLE_IF is that the latter establishes the handler only if flag evaluates to nonzero (true); otherwise, both macros behave the same way. Therefore, in the discussion that follows, only TIX_HANDLE is mentioned.

An exception handler generally requires two statement blocks, the block in which the exception can occur and the block that actually handles the exception. When establishing a TIX exception handler, use TIX_HANDLE and TIX_END_HANDLE to enclose both blocks and TIX_EXCEPTION to separate them:

TIX_HANDLE(exception) { 
      // exception block 
} TIX_EXCEPTION { 
      // handler block 
} TIX_END_HANDLE 
The braces enclosing the statement blocks are not required, but some source editors might complain if you do not use them.

If the exception is signaled during execution of the statements in the exception block, control passes from the statement that caused the exception to the first statement in the handler block. Otherwise, control passes from the last statement in the exception block to the first statement following TIX_END_HANDLE. For a simple example, see the exception handler used in main.cc, lines 20-28, listed in An Example Application.

If the specified exception is signaled but there is no handler block, control passes directly to the first statement following the TIX_END_HANDLE macro, as if the exception were handled. See Handling Exceptions Outside a Handler.

For reference information about the TIX exception handler macros, see the following in the C++ API Reference:

Handling Multiple Exceptions

You can nest TIX exception handlers to handle more than one exception in the same statement block. The structure of a nested exception handler looks like the following:

TIX_HANDLE(exception1) { 
      TIX_HANDLE(exception2) { 
            . . . 
            TIX_HANDLE(exceptionN) { 
                  // exception block 
            } TIX_EXCEPTION { 
                  // handler block for exceptionN 
            } TIX_END_HANDLE 
      } TIX_EXCEPTION { 
            // handler block for exception2 
      } TIX_END_HANDLE 
} TIX_EXCEPTION {
      // handler block for exception1 
} TIX_END_HANDLE 
You can also handle an entire group of exceptions. As explained in Predefined Exceptions, the TIX exceptions are organized in groups by the parent exception. To handle a family of exceptions, specify the name of the parent in the TIX_HANDLE macro, as follows:

TIX_HANDLE(parent-exception) { 
      // exception block 
} TIX_EXCEPTION { 
      // handler block for all child exceptions under parent-exception 
} TIX_END_HANDLE 
You might want to handle some child exceptions specifically and others generically. To do so, create a nested exception handler, and specify the parent exception in the outer nest and each of the child exceptions you want to handle in the inner nests, as follows:

TIX_HANDLE(parent-exception) { 
      TIX_HANDLE(child-exception1) { 
            TIX_HANDLE(child-exception2) { 
                  . . . 
                  TIX_HANDLE(child-exceptionN) { 
                        // exception block 
                  } TIX_EXCEPTION { 
                        // handler block for child-exceptionN 
                  } END_TIX_HANDLE 
            } TIX_EXCEPTION { 
                  // handler block for child-exception2 
            } TIX_END_HANDLE 
      } TIX_EXCEPTION { 
            // handler block for child-exception1 
      } TIX_END_HANDLE 
} TIX_EXCEPTION { 
      // handler block for all other children of parent-exception 
} TIX_END_HANDLE 
Example
The following example illustrates a nested TIX exception handler that handles two child exceptions, err_database_not_found and err_invalid_pathname, and the parent exception, err_objectstore. Both exceptions are children of the parent exception. The program expects the user to supply the name of the database file, stuff, on the command line. The program signals and handles an exception if the user supplies the wrong name or no name.

#include <ostore/ostore.hh> 
#include <fstream.h> 

main(int argc, char **argv) 
{ 
      objectstore::initialize(); 
      TIX_HANDLE (err_objectstore) { // parent exception 
            // the next two exceptions are descended from err_objectstore 
            TIX_HANDLE (err_database_not_found) { // bad name 
                  TIX_HANDLE (err_invalid_pathname)  { // no argument (null) 
                        os_database* db = os_database::open(argv[1]); 
                        db->close(); 
                        cout << "The database named " << argv[1] << " exists.\n"; 
                  } TIX_EXCEPTION { 
                        cout << "Usage:  " << argv[0] << " <database name>\n";
                  } TIX_END_HANDLE 
            } TIX_EXCEPTION { 
                  cout << "Sorry, no such database. Try again.\n"; 
            } TIX_END_HANDLE 
      } TIX_EXCEPTION { 
            // get a full report on the exception 
            cout << tix_handler::get_report(); 
      } TIX_END_HANDLE 
} 
Following is the output from a sample run:

$ tix_nest 
Usage:  tix_nest <database name> 
$ tix_nest stuff 
Sorry, no such database. Try again. 
$ tix_nest real_db 
The database named real_db exists. 
Available on line
This example program is available on line in

Handling Exceptions Outside a Handler

Occasionally, it might not be possible or convenient to handle an exception inside the handler, either because the application must continue with normal processing before handling the exception or because the handler is too restrictive to allow proper handling. In such instances, you can write a TIX exception handler without the handler block.

As mentioned in Establishing a TIX Exception Handler, the handler block can be omitted from a TIX exception handler. If an exception occurs in a handler that does not have a handler block, program control passes from the statement in the exception block that caused the exception to the first statement outside the handler. This behavior effectively clears the exception.

To illustrate, consider a handler that prompts the user for a file name if the application fails to open either of two database files with the supplied names. The handler can never prompt for the names of both files, even if the names are both wrong. The reason is that the exception generated by the failed attempt to open the first file would prevent the application from trying to open the second file.

The solution is to enclose the statements that open the database files each in its own handler, without supplying a handler block for either one. Without the handler block, the application tries opening both files, even if it fails to open the first. The code to handle the error conditions follows the second handler, where the application tests whether the files were opened successfully. Following is the coding for the handler:

Example
The following example program illustrates this type of exception handler:

// no_hndlr.cc: Uses a handler-less TIX exception handler 
// to handle exception signaled if one or the other or both of the 
// databases specified on command line do not exist 
#include <ostore/ostore.hh> 
#include <fstream.h> 

main(int argc, char** argv) 
{ 
      OS_ESTABLISH_FAULT_HANDLER { 
            if(argc!=3) { // check for two command-line arguments 
                  cout << "Usage:  " << argv[0]  << " <db1> <db2>\n"; 
                  return 1; 
            } 

            objectstore::initialize(); 
            // set both os_database pointers to 0 so we can check later to 
            // see which database we were able to open, if any 
            os_database *db1 = 0, *db2 = 0; 
            TIX_HANDLE (err_database_not_found) { 
                  db1 = os_database::open(argv[1]); // open first db 
            } TIX_EXCEPTION { 
                  // no handler code 
            } TIX_END_HANDLE 
            TIX_HANDLE (err_database_not_found) { 
                  db2 = os_database::open(argv[2]); // open second db 
            } TIX_EXCEPTION { 
                  // no handler code 
            } TIX_END_HANDLE 
            // check if files were opened; null means the open failed 
            if (!db1 || !db2) { 
                  cerr << "Cannot open "; 
                  if (db2) 
                        cerr << argv[1]; // failed to open first db 
                  else if (db1) 
                        cerr << argv[2]; // failed to open second db 
                  else // couldn't open either one 
                        cerr << argv[1] << " or " << argv[2]; 
                  cerr << endl; 
                  return 1; 
            } 
            else // both databases exist 
                  cout << "Opened " << argv[1] << " and " << argv[2] << endl; 
      } OS_END_FAULT_HANDLER 
      return 0; 
} 
Following is the output from a sample run; it assumes that that no_db1 and no_db2 do not exist and that real_db1 and real_db2 do exist:

$ no_hndlr no_db1 real_db2 
Cannot open no_db1 
$ no_hndlr real_db1 no_db2 
Cannot open no_db2 
$ no_hndlr no_db1 no_db2 
Cannot open no_db1 or no_db2 
Available on line
This example program is available on line in

Using the TIX Classes

ObjectStore provides the following classes for handling TIX exceptions:

basic_undo 
objectstore_exception 
os_lock_timeout_exception 
tix_context 
tix_exception 
tix_handler 
These classes fill out the functionality of TIX exception handling, providing services such as explicit signal control, debugging tools, and error-message formatting. The following sections describe the more commonly used of these features.

For detailed information about the TIX classes and their member functions, see the following in the C++ API Reference:

Signaling Exceptions

All TIX exceptions - predefined as well as user-defined - are instances of objectstore_exception, one of whose member functions is signal(). This function enables you to signal explicitly any TIX exception, including any that you define. Note that, unless you call signal() inside a TIX exception handler, the program never returns from the signaled exception.

The signal() function is useful when the application can detect an error condition before ObjectStore can. Instead of performing superfluous processing, it signals the exception. For example, an application can detect that a string that holds the name of a database is empty. Instead of trying to open the file, it can signal err_invalid_pathname or any other appropriate exception, as follows:

if (!*name) 
      err_invalid_pathname.signal("The string you supplied for 
filename is empty."); 
and handle the exception.

signal() also performs printf-like formatting for any additional messages you might want to display. signal() thus enables you to give more specific information about the exception and program state at the time of the exception.

The message is displayed only if the exception is not handled. If you call signal() inside a handler, you can supply an empty string ("") for the message argument.

For more information about signal(), see objectstore_exception::signal() in the C++ API Reference.

Displaying Additional Error Information

ObjectStore provides two classes for displaying additional information about an exception, tix_context and tix_handler. For more information about these classes, see the following in the C++ API Reference:

tix_context
Instances of the class tix_context can be used to display information about the context in which an exception occurs. Because tix_context variables are automatic, they are active only in the block in which they are declared, and they do not cause memory leaks. Also, the string you supply as an argument to the constructor for a tix_context variable is displayed only when an exception occurs in the same scoping block.

Consider, for example, the following declaration of tc_var:

{ // block A 
      . . . 
      { // block B 
            tix_context tc_var("Hello, world\n"); 
            . . . 
            { // block C 
                  . . . 
            } 
      }
} 
The message Hello, world appears only if an exception occurs in block B. The message appears immediately after the message displayed by the exception.

tix_context can be useful as a debugging tool, similar to the printf statement. That is, you can declare tix_context variables in different parts of your program that you suspect of causing an exception, so that a formatted message is displayed only if an exception occurs in the same scope.

tix_handler
When you handle an exception inside a TIX exception handler, ObjectStore does not display any information about the exception. However, the error-message string displayed by ObjectStore when no handler is present can contain useful information about the error that the handler might want to log into an error file. The tix_handler class provides two static function members that return this string, get_report() and get_report0(). The only difference between them is that the string returned by get_report0() does not include the error message associated with the exception.

For more information about get_report() and get_report0(), see the following in the C++ API Reference:

The following handler displays its own error message when it handles the exception err_database_not_found:

TIX_HANDLE (err_database_not_found) { 
      db1 = os_database::open(path); 
} TIX_EXCEPTION { 
      // cerr << tix_handler::get_report(); 
      cerr << "Bad file name\n"; 
} TIX_END_HANDLE 
Following is the output:

Bad file name 
After tix_handler::get_report() is uncommented, the handler displays the following:

The database was not found 
handcuff:/h/handcuff/2/jimmy/debug/test_tix/stuff 
(err_database_not_found) 
Bad file name 
For another example of how to use tix_handler::get_report(), see the example program listed in Handling Multiple Exceptions.



[previous] [next]

Copyright © 1999 Object Design, Inc. All rights reserved.

Updated: 03/09/99 13:54:54