What happens if an exception is thrown in a method with a try block that has no matching catch block?

This chapter explains exception handling as it is currently implemented in the Sun C++ compiler and discusses the requirements of the C++ International Standard.

Show

For additional information on exception handling, see The C++ Programming Language, Third Edition, by Bjarne Stroustrup (Addison-Wesley, 1997).

5.1 Understanding Exception Handling

Exceptions are anomalies that occur during the normal flow of a program and prevent it from continuing. These anomalies--user, logic, or system errors--can be detected by a function. If the detecting function cannot deal with the anomaly, it "throws" an exception. A function that "handles" that kind of exception catches it.

In C++, when an exception is thrown, it cannot be ignored--there must be some kind of notification or termination of the program. If no user-provided exception handler is present, the compiler provides a default mechanism to terminate the program.

Exception handling is expensive compared to ordinary program flow controls, such as loops or if-statements. It is therefore better not to use the exception mechanism to deal with ordinary situations, but to reserve it for situations that are truly unusual.

Exceptions are particularly helpful in dealing with situations that cannot be handled locally. Instead of propagating error status throughout the program, you can transfer control directly to the point where the error can be handled.

For example, a function might have the job of opening a file and initializing some associated data. If the file cannot be opened or is corrupted, the function cannot do its job. However, that function might not have enough information to handle the problem. The function can throw an exception object that describes the problem, transferring control to an earlier point in the program. The exception handler might automatically try a backup file, query the user for another file to try, or shut down the program gracefully. Without exception handlers, status and data would have to be passed down and up the function call hierarchy, with status checks after every function call. With exception handlers, the flow of control is not obscured by error checking. If a function returns, the caller can be certain that it succeeded.

Exception handlers have disadvantages. If a function does not return because it, or some other function it called, threw an exception, data might be left in an inconsistent state. You need to know when an exception might be thrown, and whether the exception might have a bad effect on the program state.

For information about using exceptions in a multithreaded environment, see Section 9.2 Using Exceptions in a Multithreaded Program.

5.2 Using Exception Handling Keywords

There are three keywords for exception handling in C++:

5.2.1 try

A try block is a group of C++ statements, enclosed in braces { }, that might cause an exception. This grouping restricts exception handlers to exceptions generated within the try block. Each try block has one or more associated catch blocks.

5.2.2 catch

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:

  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 braces { }, whose purpose is to handle the exception

5.2.3 throw

The throw statement is used to throw an exception and its value to a subsequent 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 (with no expression).

In the following example, the function call in the try block passes control to f(), which throws an exception of type Overflow. This exception is handled by the catch block, which handles type Overflow exceptions.

class Overflow { // ... public: Overflow(char,double,double); }; void f(double x) { // ... throw Overflow('+',x,3.45e107); } int main() { try { // ... f(1.2); //... } catch(Overflow& oo) { // handle exceptions of type Overflow here } }

5.3 Implementing Exception Handlers

To implement an exception handler, perform these basic tasks:

  • When a function is called by many other functions, code it so that an exception is thrown whenever an error is detected. The throw expression throws an object. This object is used to identify the types of exceptions and to pass specific information about the exception that has been thrown.
  • Use the try statement in a client program to anticipate exceptions. Enclose function calls that might produce an exception in a try block.
  • Code one or more catch blocks immediately after the try block. Each catch block identifies what type or class of objects it is capable of catching. When an object is thrown by the exception, the following actions occur:

    • If the object thrown by the exception matches the type of the catch expression, control passes to that catch block.
    • If the object thrown by the exception does not match the first catch block, subsequent catch blocks are searched for a matching type (see Section 5.8 "Matching Exceptions With Handlers"").
    • If there is no catch block at the current scope matching the thrown exception, the current scope is exited, and all automatic (local nonstatic) objects defined in that scope are destroyed. The surrounding scope (which might be function scope) is checked for a matching handler. This process is continued until a scope is found that has a matching catch block. If one is found before exiting function main(), that catch block is entered.
    • If there is no match in any of the catch blocks, the program is normally terminated with a call to the predefined function terminate(). By default, terminate() calls abort(), which destroys all remaining objects and exits from the program. This default behavior can be changed by calling the set_terminate() function.

5.3.1 Synchronous Exception Handling

Exception handling is designed to support only synchronous exceptions, such as array range checks. The term synchronous exception means that exceptions can only be originated from throw expressions.

The C++ standard supports synchronous exception handling with a termination model. Termination means that once an exception is thrown, control never returns to the throw point.

5.3.2 Asynchronous Exception Handling

Exception handling is not designed to directly handle asynchronous exceptions such as keyboard interrupts. However, you can make exception handling work in the presence of asynchronous events if you are careful. For instance, to make exception handling work with signals, you can write a signal handler that sets a global variable, and create another routine that polls the value of that variable at regular intervals and throws an exception when the value changes. You cannot throw an exception from a signal handler.

5.4 Managing Flow of Control

In C++, exception handlers do not correct the exception and then return to the point at which the exception occurred. Instead, when an exception is generated, control is passed out of the block that threw the exception, out of the try block that anticipated the exception, and into the catch block whose exception declaration matches the exception thrown.

The catch block handles the exception. It might rethrow the same exception, throw another exception, jump to a label, return from the function, or end normally. If a catch block ends normally, without a throw, the flow of control passes over all other catch blocks associated with the try block.

Whenever an exception is thrown and caught, and control is returned outside of the function that threw the exception, stack unwinding takes place. During stack unwinding, any automatic objects that were created within the scope of the block that was exited are safely destroyed via calls to their destructors.

If a try block ends without an exception, all associated catch blocks are ignored.

Note – An exception handler cannot return control to the source of the error by using the return statement. A return statement issued in this context returns from the function containing the catch block.

5.4.1 Branching Into and Out of try Blocks and Handlers

Branching out of a try block or a handler is allowed. Branching into a catch block is not allowed, however, because that is equivalent to jumping past an initiation of the exception.

5.4.2 Nesting of Exceptions

Nesting of exceptions, that is, throwing an exception while another remains unhandled, is allowed only in restricted circumstances. From the point when an exception is thrown to the point when the matching catch clause is entered, the exception is unhandled. Functions that are called along the way, such as destructors of automatic objects being destroyed, may throw new exceptions, as long as the exception does not escape the function. If a function exits via an exception while another exception remains unhandled, the terminate() function is called immediately.

Once an exception handler has been entered, the exception is considered handled, and exceptions may be thrown again.

You can determine whether any exception has been thrown and is currently unhandled. See Section 5.7 Calling the uncaught_exception() Function.

5.4.3 Specifying Exceptions to Be Thrown

A function declaration can include an exception specification, a list of exceptions that a function may throw, directly or indirectly.

The two following declarations indicate to the caller that the function f1 generates only exceptions that can be caught by a handler of type X, and that the function f2 generates only exceptions that can be caught by handlers of type W, Y, or Z:

void f1(int) throw(X); void f2(int) throw(W,Y,Z);

A variation on the previous example is:

void f3(int) throw(); // empty parentheses

This declaration guarantees that no exception is generated by the function f3. If a function exits through any exception that is not allowed by an exception specification, it results in a call to the predefined function unexpected(). By default, unexpected() calls terminate() which by default exits the program. You can change this default behavior by calling the set_unexpected() function. See Section 5.6.2 set_unexpected().

The check for unexpected exceptions is done at program execution time, not at compile time. Even if it appears that a disallowed exception might be thrown, there is no error unless the disallowed exception is actually thrown at runtime.

The compiler can, however, eliminate unnecessary checking in some simple cases. For instance, no checking for f is generated in the following example.

void foo(int) throw(x); void f(int) throw(x); { foo(13); }

The absence of an exception specification allows any exception to be thrown.

5.5 Specifying Runtime Errors

There are five runtime error messages associated with exceptions:

  • No handler for the exception
  • Unexpected exception thrown
  • An exception can only be re-thrown in a handler
  • During stack unwinding, a destructor must handle its own exception
  • Out of memory

When errors are detected at runtime, the error message displays the type of the current exception and one of the five error messages. By default, the predefined function terminate() is called, which then calls abort().

The compiler uses the information provided in the exception specification to optimize code production. For example, table entries for functions that do not throw exceptions are suppressed, and runtime checking for exception specifications of functions is eliminated wherever possible. Thus, declaring functions with correct exception specifications can lead to better code generation.

5.6 Modifying the terminate() and unexpected() Functions

The following sections describe how to modify the behavior of the terminate() and unexpected() functions using set_terminate() and set_unexpected(). For information about using these functions in a multithreaded environment, see Section 9.2 Using Exceptions in a Multithreaded Program.

5.6.1 set_terminate()

You can modify the default behavior of terminate() by calling the function set_terminate(), as shown in the following example.

// declarations are in standard header <exception> namespace std { typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler f) throw(); void terminate(); }

The terminate() function is called in any of the following circumstances:

  • The exception handling mechanism calls a user function (including destructors for automatic objects) that exits through an uncaught exception while another exception remains uncaught.
  • The exception handling mechanism cannot find a handler for a thrown exception.
  • The construction or destruction of a nonlocal object with static storage duration exits using an exception.
  • Execution of a function registered with atexit() exits using an exception.
  • A throw expression with no operand attempts to rethrow an exception and no exception is being handled.
  • The unexpected() function throws an exception that is not allowed by the previously violated exception specification, and std::bad_exception is not included in that exception specification.
  • The default version of unexpected() is called.

The terminate() function calls the function passed as an argument to set_terminate(). Such a function takes no parameters, returns no value, and must terminate the program (or the current thread). The function passed in the most recent call to set_terminate() is called. The previous function passed as an argument to set_terminate() is the return value, so you can implement a stack strategy for using terminate(). The default function for terminate() calls abort() for the main thread and thr_exit() for other threads. Note that thr_exit() does not unwind the stack or call C++ destructors for automatic objects.

Note – A replacement for terminate() must not return to its caller.

5.6.2 set_unexpected()

You can modify the default behavior of unexpected() by calling the function set_unexpected(), as shown in the following example.

// declarations are in standard header <exception> namespace std { class exception; class bad_exception; typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler f) throw(); void unexpected(); }

The unexpected() function is called when a function attempts to exit through an exception not listed in its exception specification. The default version of unexpected() calls terminate().

A replacement version of unexpected() might throw an exception permitted by the violated exception specification. If it does so, exception handling continues as though the original function had really thrown the replacement exception. If the replacement for unexpected() throws any other exception, that exception is replaced by the standard exception std::bad_exception. If the original function's exception specification does not allow std::bad_exception, function terminate() is called immediately. Otherwise, exception handling continues as though the original function had really thrown std::bad_exception.

unexpected() calls the function passed as an argument to set_unexpected(). Such a function takes no parameters, returns no value, and must not return to its caller. The function passed in the most recent call to set_unexpected() is called. The previous function passed as an argument to set_unexpected() is the return value, so you can implement a stack strategy for using unexpected().

Note – A replacement for unexpected() must not return to its caller.

5.7 Calling the uncaught_exception() Function

An uncaught, or active, exception is an exception that has been thrown, but not yet accepted by a handler. The function uncaught_exception() returns true if there is an uncaught exception, and false otherwise.

The uncaught_exception() function is most useful for preventing program termination due to a function that exits with an uncaught exception while another exception is still active. This situation most commonly occurs when a destructor called during stack unwinding throws an exception. To prevent this situation, make sure uncaught_exception() returns false before throwing an exception within a destructor. (Another way to prevent such termination is to design your program so that destructors do not need to throw exceptions.)

5.8 Matching Exceptions With Handlers

A handler type T matches a throw type E if any one of the following is true:

  • T is the same as E.
  • T is const or volatile of E.
  • E is const or volatile of T.
  • T is ref of E or E is ref of T.
  • T is a public base class of E.
  • T and E are both pointer types, and E can be converted to T by a standard pointer conversion.

Throwing exceptions of reference or pointer types can result in a dangling pointer if the object pointed or referred to is destroyed before exception handling is complete. When an object is thrown, a copy of the object is always made through the copy constructor, and the copy is passed to the catch block. It is therefore safe to throw a local or temporary object.

While handlers of type (X) and (X&) both match an exception of type X, the semantics are different. Using a handler with type (X) invokes the object's copy constructor (again). If the thrown object is of a type derived from the handler type, the object is truncated. Catching a class object by reference therefore usually executes faster.

Handlers for a try block are tried in the order of their appearance. Handlers for a derived class (or a pointer to a reference to a derived class) must precede handlers for the base class to ensure that the handler for the derived class can be invoked.

5.9 Checking Access Control in Exceptions

The compiler performs the following check on access control for exceptions:

  • The formal argument of a catch clause obeys the same rules as an argument of the function in which the catch clause occurs.
  • An object can be thrown if it can be copied and destroyed in the context of the function in which the throw occurs.

Currently, access controls do not affect matching.

No other access is checked at runtime except for the matching rule described in Section 5.8 "Matching Exceptions With Handlers"".

5.10 Enclosing Functions in try Blocks

If the constructor for a base class or member of a class T exits via an exception, there would ordinarily be no way for the T constructor to detect or handle the exception. The exception would be thrown before the body of the T constructor is entered, and thus before any try block in T could be entered.

A new feature in C++ is the ability to enclose an entire function in a try block. For ordinary functions, the effect is no different from placing the body of the function in a try block. But for a constructor, the try block traps any exceptions that escape from initializers of base classes and members of the constructor's class. When the entire function is enclosed in a try block, the block is called a function try block.

In the following example, any exception thrown from the constructor of base class B or member e is caught before the body of the T constructor is entered, and is handled by the matching catch block.

You cannot use a return statement in the handler of a function try block, because the catch block is outside the function. You can only throw an exception or terminate the program by calling exit() or terminate().

class B { ... }; class E { ... }; class T : public B { public: T(); private: E e;

};

T::T()
try : B(args), e(args) {

... // body of constructor
}

catch( X& x ) {

... // handle exception X
}

catch( ... ) {

... // handle any other exception
}

5.11 Disabling Exceptions

If you know that exceptions are not used in a program, you can use the compiler option -features=no%except to suppress generation of code that supports exception handling. The use of the option results in slightly smaller code size and faster code execution. However, when files compiled with exceptions disabled are linked to files using exceptions, some local objects in the files compiled with exceptions disabled are not destroyed when exceptions occur. By default, the compiler generates code to support exception handling. Unless the time and space overhead is important, it is usually better to leave exceptions enabled.

5.12 Using Runtime Functions and Predefined Exceptions

The standard header <exception> provides the classes and exception-related functions specified in the C++ standard. You can access this header only when compiling in standard mode (compiler default mode, or with option -compat=5). The following excerpt shows the <exception> header file declarations.

// standard header <exception> namespace std { class exception { exception() throw(); exception(const exception&) throw(); exception& operator=(const exception&) throw(); virtual ~exception() throw(); virtual const char* what() const throw();

};

class bad_exception: public exception { ... }; // Unexpected exception handling typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler) throw();

void unexpected();

// Termination handling typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler) throw();

void terminate();

bool uncaught_exception() throw();
}

The standard class exception is the base class for all exceptions thrown by selected language constructs or by the C++ standard library. An object of type exception can be constructed, copied, and destroyed without generating an exception. The virtual member function what() returns a character string that describes the exception.

For compatibility with exceptions as used in C++ release 4.2, the header <exception.h> is also provided for use in standard mode. This header allows for a transition to standard C++ code and contains declarations that are not part of standard C++. Update your code to follow the C++ standard (using <exception> instead of <exception.h>) as development schedules permit.

// header <exception.h>, used for transition #include <exception> #include <new> using std::exception; using std::bad_exception; using std::set_unexpected; using std::unexpected; using std::set_terminate;

using std::terminate;

typedef std::exception xmsg; typedef std::bad_exception xunexpected; typedef std::bad_alloc xalloc;

In compatibility mode (--compat[=4]), header <exception> is not available, and header <exception.h> refers to the same header provided with C++ release 4.2. It is not reproduced here.

5.13 Mixing Exceptions With Signals and Setjmp/Longjmp

You can use setjmp/longjmp in a program where exceptions can occur, as long as they don't interact.

All the rules for using exceptions and setjmp/longjmp separately apply. In addition, a longjmp from point A to point B is valid only if an exception thrown at A and caught at B would have the same effect. In particular, you must not longjmp into or out of a try-block or catch-block (directly or indirectly), or longjmp past the initialization or non-trivial destruction of auto or temporary variables.

You cannot throw an exception from a signal handler.

5.14 Building Shared Libraries That Have Exceptions

When shared libraries are opened with dlopen, you must use RTLD_GLOBAL for exceptions to work.

Note – When building shared libraries that contain exceptions, do not pass the option -Bsymbolic to ld. Exceptions that should be caught might be missed.


Page 2

This chapter explains the use of Runtime Type Identification (RTTI). Use this feature while a program is running to find out type information that you could not determine at compile time.

6.1 Static and Dynamic Types

In C++, pointers to classes have a static type, the type written in the pointer declaration, and a dynamic type, which is determined by the actual type referenced. The dynamic type of the object could be any class type derived from the static type. In the following example, ap has the static type A* and a dynamic type B*.

class A {}; class B: public A {}; extern B bv; extern A* ap = &bv;

RTTI allows the programmer to determine the dynamic type of the pointer.

6.2 RTTI Options

In compatibility mode (-compat[=4]), RTTI support requires significant resources to implement. RTTI is disabled by default in that mode. To enable RTTI implementation and recognition of the associated typeid keyword, use the option -features=rtti. To disable RTTI implementation and recognition of the associated typeid keyword, use the option --features=no%rtti (the default).

In standard mode (the default mode), RTTI does not have a significant impact on program compilation or execution. RTTI is always enabled in standard mode.

6.3 typeid Operator

The typeid operator produces a reference to an object of class type_info, which describes the most-derived type of the object. To make use of the typeid() function, the source code must #include the <typeinfo> header file. The primary value of this operator and class combination is in comparisons. In such comparisons, the top-level const and volatile qualifiers are ignored, as in the following example. Note that, in this example, A and B are types which have default constructors.

#include <typeinfo> #include <assert.h> void use_of_typeinfo( ) { A a1; const A a2; assert( typeid(a1) == typeid(a2) ); assert( typeid(A) == typeid(const A) ); assert( typeid(A) == typeid(a2) ); assert( typeid(A) == typeid(const A&) ); B b1; assert( typeid(a1) != typeid(b1) ); assert( typeid(A) != typeid(B) ); }

The typeid operator throws a bad_typeid exception when given a null pointer.

6.4 type_info Class

The class type_info describes type information generated by the typeid operator. The primary functions provided by type_info are equality, inequality, before and name. From <typeinfo.h>, the definition is:

class type_info { public: virtual ~type_info( ); bool operator==( const type_info &rhs ) const; bool operator!=( const type_info &rhs ) const; bool before( const type_info &rhs ) const; const char *name( ) const; private: type_info( const type_info &rhs ); type_info &operator=( const type_info &rhs ); };

The before function compares two types relative to their implementation-dependent collation order. The name function returns an implementation-defined, null-terminated, multibyte string, suitable for conversion and display.

The constructor is a private member function, so you cannot create a variable of type type_info. The only source of type_info objects is in the typeid operator.


Page 3

This chapter discusses the new cast operators in the C++ standard: const_cast, reinterpret_cast, static_cast and dynamic_cast. A cast converts an object or value from one type to another.

7.1 New Cast Operations

The C++ standard defines new cast operations that provide finer control than previous cast operations. The dynamic_cast<> operator provides a way to check the actual type of a pointer to a polymorphic class. You can search with a text editor for all new-style casts (search for _cast), whereas finding old-style casts required syntactic analysis.

Otherwise, the new casts all perform a subset of the casts allowed by the classic cast notation. For example, const_cast<int*>(v) could be written (int*)v. The new casts simply categorize the variety of operations available to express your intent more clearly and allow the compiler to provide better checking.

The cast operators are always enabled. They cannot be disabled.

7.2 const_cast

The expression const_cast<T>(v) can be used to change the const or volatile qualifiers of pointers or references. (Among new-style casts, only const_cast<> can remove const qualifiers.) T must be a pointer, reference, or pointer-to-member type.

class A { public: virtual void f(); int i; }; extern const volatile int* cvip; extern int* ip; void use_of_const_cast( ) { const A a1; const_cast<A&>(a1).f( ); // remove const ip = const_cast<int*> (cvip); // remove const and volatile }

7.3 reinterpret_cast

The expression reinterpret_cast<T>(v)changes the interpretation of the value of the expression v. It can be used to convert between pointer and integer types, between unrelated pointer types, between pointer-to-member types, and between pointer-to-function types.

Usage of the reinterpret_cast operator can have undefined or implementation-dependent results. The following points describe the only ensured behavior:

  • A pointer to a data object or to a function (but not a pointer to member) can be converted to any integer type large enough to contain it. (Type long is always large enough to contain a pointer value on the architectures supported by Sun WorkShop C++.) When converted back to its original type, the value will be the same as it originally was.
  • A pointer to a (nonmember) function can be converted to a pointer to a different (nonmember) function type. If converted back to the original type, the value will be the same as it originally was.
  • A pointer to an object can be converted to a pointer to a different object type, provided that the new type has alignment requirements no stricter than the original type. If converted back to the original type, the value will be the same as it originally was.
  • An lvalue of type T1 can be converted to a type "reference to T2" if an expression of type "pointer to T1" can be converted to type "pointer to T2" with a reinterpret cast.
  • An rvalue of type "pointer to member of X of type T1" can be explicitly converted to an rvalue of type "pointer to member of Y of type T2" if T1 and T2 are both function types or both object types.
  • In all allowed cases, a null pointer of one type remains a null pointer when converted to a null pointer of a different type.
  • The reinterpret_cast operator cannot be used to cast away const; use const_cast for that purpose.
  • The reinterpret_cast operator should not be used to convert between pointers to different classes that are in the same class hierarchy; use a static or dynamic cast for that purpose. (reinterpret_cast does not perform the adjustments that might be needed.) This is illustrated in the following example:

    class A { int a; public: A(); }; class B : public A { int b, c; }; void use_of_reinterpret_cast( ) { A a1; long l = reinterpret_cast<long>(&a1); A* ap = reinterpret_cast<A*>(l); // safe B* bp = reinterpret_cast<B*>(&a1); // unsafe const A a2; ap = reinterpret_cast<A*>(&a2); // error, const removed }

7.4 static_cast

The expression static_cast<T>(v) converts the value of the expression v to type T. It can be used for any type conversion that is allowed implicitly. In addition, any value can be cast to void, and any implicit conversion can be reversed if that cast would be legal as an old-style cast.

class B { ... }; class C : public B { ... }; enum E { first=1, second=2, third=3 }; void use_of_static_cast(C* c1 ) { B* bp = c1; // implicit conversion C* c2 = static_cast<C*>(bp); // reverse implicit conversion int i = second; // implicit conversion

E e = static_cast<E>(i); // reverse implicit conversion

}

The static_cast operator cannot be used to cast away const. You can use static_cast to cast "down" a hierarchy (from a base to a derived pointer or reference), but the conversion is not checked; the result might not be usable. A static_cast cannot be used to cast down from a virtual base class.

7.5 Dynamic Casts

A pointer (or reference) to a class can actually point (refer) to any class derived from that class. Occasionally, it may be desirable to obtain a pointer to the fully derived class, or to some other subobject of the complete object. The dynamic cast provides this facility.

Note – When compiling in compatibility mode (-compat[=4]), you must compile with -features=rtti if your program uses dynamic casts.

The dynamic type cast converts a pointer (or reference) to one class T1 into a pointer (reference) to another class T2. T1 and T2 must be part of the same hierarchy, the classes must be accessible (via public derivation), and the conversion must not be ambiguous. In addition, unless the conversion is from a derived class to one of its base classes, the smallest part of the hierarchy enclosing both T1 and T2 must be polymorphic (have at least one virtual function).

In the expression dynamic_cast<T>(v), v is the expression to be cast, and T is the type to which it should be cast. T must be a pointer or reference to a complete class type (one for which a definition is visible), or a pointer to cv void, where cv is an empty string, const, volatile, or const volatile.

7.5.1 Casting Up the Hierarchy

When casting up the hierarchy, if T points (or refers) to a base class of the type pointed (referred) to by v, the conversion is equivalent to static_cast<T>(v).

7.5.2 Casting to void*

If T is void*, the result is a pointer to the complete object. That is, v might point to one of the base classes of some complete object. In that case, the result of dynamic_cast<void*>(v) is the same as if you converted v down the hierarchy to the type of the complete object (whatever that is) and then to void*.

When casting to void*, the hierarchy must be polymorphic (have virtual functions). The result is checked at runtime.

7.5.3 Casting Down or Across the Hierarchy

When casting down or across the hierarchy, the hierarchy must be polymorphic (have virtual functions). The result is checked at runtime.

The conversion from v to T is not always possible when casting down or across a hierarchy. For example, the attempted conversion might be ambiguous, T might be inaccessible, or v might not point (or refer) to an object of the necessary type. If the runtime check fails and T is a pointer type, the value of the cast expression is a null pointer of type T. If T is a reference type, nothing is returned (there are no null references in C++), and the standard exception std::bad_cast is thrown.

For example, this example of public derivation succeeds:

class A { public: virtual void f(); }; class B { public: virtual void g(); }; class AB : public virtual A, public B { }; void simple_dynamic_casts( ) {   AB ab;   B* bp = &ab; // no casts needed   A* ap = &ab;   AB& abr = dynamic_cast<AB&>(*bp); // succeeds   ap = dynamic_cast<A*>(bp); assert( ap != NULL );   bp = dynamic_cast<B*>(ap); assert( bp != NULL );   ap = dynamic_cast<A*>(&abr); assert( ap != NULL );   bp = dynamic_cast<B*>(&abr); assert( bp != NULL ); }

whereas this example fails because base class B is inaccessible.

class A { public: virtual void f(); }; class B { public: virtual void g(); }; class AB : public virtual A, private B { }; void attempted_casts( ) {   AB ab;   B* bp = (B*)&ab; // C-style cast needed to break protection   A* ap = dynamic_cast<A*>(bp); // fails, B is inaccessible   assert(ap == NULL);   AB& abr = dynamic_cast<AB&>(*bp);   try {     AB& abr = dynamic_cast<AB&>(*bp); // fails, B is inaccessible   }   catch(const bad_cast&) {     return; // failed reference cast caught here   }   assert(0); // should not get here }

In the presence of virtual inheritance and multiple inheritance of a single base class, the actual dynamic cast must be able to identify a unique match. If the match is not unique, the cast fails. For example, given the additional class definitions:

class AB_B : public AB, public B { }; class AB_B__AB : public AB_B, public AB { };

Example:

void complex_dynamic_casts( ) { AB_B__AB ab_b__ab; A*ap = &ab_b__ab; // okay: finds unique A statically AB*abp = dynamic_cast<AB*>(ap); // fails: ambiguous assert( abp == NULL ); // STATIC ERROR: AB_B* ab_bp = (AB_B*)ap; // not a dynamic cast AB_B*ab_bp = dynamic_cast<AB_B*>(ap); // dynamic one is okay assert( ab_bp != NULL ); }

The null-pointer error return of dynamic_cast is useful as a condition between two bodies of code--one to handle the cast if the type guess is correct, and one if it is not.

void using_dynamic_cast( A* ap ) { if ( AB *abp = dynamic_cast<AB*>(ap) ) { // abp is non-null, // so ap was a pointer to an AB object // go ahead and use abp process_AB( abp ); } else { // abp is null, // so ap was NOT a pointer to an AB object // do not use abp process_not_AB( ap ); } }

In compatibility mode (-compat[=4]), if runtime type information has not been enabled with the -features=rtti compiler option, the compiler converts dynamic_cast to static_cast and issues a warning. See Section 6.2 RTTI Options.

If exceptions have been disabled, the compiler converts dynamic_cast<T&> to static_cast<T&> and issues a warning. (A dynamic_cast to a reference type requires an exception to be thrown if the conversion is found at run time to be invalid.). For information about exceptions, see Chapter 5.

Dynamic cast is necessarily slower than an appropriate design pattern, such as conversion by virtual functions. See Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma (Addison-Wesley, 1994).


Page 4

You can improve the performance of C++ functions by writing those functions in a manner that helps the compiler do a better job of optimizing them. Many books have been written on software performance in general and C++ in particular. For example, see C++ Programming Style by Tom Cargill (Addison-Wesley, 1992), Writing Efficient Programs by Jon Louis Bentley (Prentice-Hall, 1982), Efficient C++: Performance Programming Techniques by Dov Bulka and David Mayhew (Addison-Wesley, 2000), and Effective C++--50 Ways to Improve Your Programs and Designs, Second Edition, by Scott Meyers, (Addison-Wesley, 1998). This chapter does not repeat such valuable information, but discusses only those performance techniques that strongly affect the Sun WorkShop C++ compiler.

8.1 Avoiding Temporary Objects

C++ functions often produce implicit temporary objects, each of which must be created and destroyed. For non-trivial classes, the creation and destruction of temporary objects can be expensive in terms of processing time and memory usage. The Sun WorkShop C++ compiler does eliminate some temporary objects, but it cannot eliminate all of them.

Write functions to minimize the number of temporary objects as long as your programs remain comprehensible. Techniques include using explicit variables rather than implicit temporary objects and using reference parameters rather than value parameters. Another technique is to implement and use operations such as += rather than implementing and using only + and =. For example, the first line below introduces a temporary object for the result of a + b, while the second line does not.

T x = a + b; T x( a ); x += b;

8.2 Using Inline Functions

Calls to small and quick functions can be smaller and quicker when expanded inline than when called normally. Conversely, calls to large or slow functions can be larger and slower when expanded inline than when branched to. Furthermore, all calls to an inline function must be recompiled whenever the function definition changes. Consequently, the decision to use inline functions requires considerable care.

Do not use inline functions when you anticipate changes to the function definition and recompiling all callers is expensive. Otherwise, use inline functions when the code to expand the function inline is smaller than the code to call the function or the application performs significantly faster with the function inline.

The compiler cannot inline all function calls, so making the most effective use of function inlining may require some source changes. Use the +w option to learn when function inlining does not occur. In the following situations, the compiler will not inline the function:

  • The function contains difficult control constructs, such as loops, switch statements, and try/catch statements. Many times these functions execute the difficult control constructs infrequently. To inline such a function, split the function into two parts, an inner part that contains the difficult control constructs and an outer part that decides whether or not to call the inner part. This technique of separating the infrequent part from the frequent part of a function can improve performance even when the compiler can inline the full function.
  • The inline function body is large or complicated. Apparently simple function bodies may be complicated because of calls to other inline functions within the body, or because of implicit constructor and destructor calls (as often occurs in constructors and destructors for derived classes). For such functions, inline expansion rarely provides significant performance improvement, and the function is best left uninlined.
  • The arguments to an inline function call are large or complicated. The compiler is particularly sensitive when the object for an inline member function call is itself the result of an inline function call. To inline functions with complicated arguments, simply compute the function arguments into local variables and then pass the variables to the function.

8.3 Using Default Operators

If a class definition does not declare a parameterless constructor, a copy constructor, a copy assignment operator, or a destructor, the compiler will implicitly declare them. These are called default operators. A C-like struct has these default operators. When the compiler builds a default operator, it knows a great deal about the work that needs to be done and can produce very good code. This code is often much faster than user-written code because the compiler can take advantage of assembly-level facilities while the programmer usually cannot. So, when the default operators do what is needed, the program should not declare user-defined versions of these operators.

Default operators are inline functions, so do not use default operators when inline functions are inappropriate (see the previous section). Otherwise, default operators are appropriate when:

  • The user-written parameterless constructor would only call parameterless constructors for its base objects and member variables. Primitive types effectively have "do nothing" parameterless constructors.
  • The user-written copy constructor would simply copy all base objects and member variables.
  • The user-written copy assignment operator would simply copy all base objects and member variables.
  • The user-written destructor would be empty.

Some C++ programming texts suggest that class programmers always define all operators so that any reader of the code will know that the class programmer did not forget to consider the semantics of the default operators. Obviously, this advice interferes with the optimization discussed above. The resolution of the conflict is to place a comment in the code stating that the class is using the default operator.

8.4 Using Value Classes

C++ classes, including structures and unions, are passed and returned by value. For Plain-Old-Data (POD) classes, the C++ compiler is required to pass the struct as would the C compiler. Objects of these classes are passed directly. For objects of classes with user-defined copy constructors, the compiler is effectively required to construct a copy of the object, pass a pointer to the copy, and destruct the copy after the return. Objects of these classes are passed indirectly. For classes that fall between these two requirements, the compiler can choose. However, this choice affects binary compatibility, so the compiler must choose consistently for every class.

For most compilers, passing objects directly can result in faster execution. This execution improvement is particularly noticeable with small value classes, such as complex numbers or probability values. You can sometimes improve program efficiency by designing classes that are more likely to be passed directly than indirectly.

In compatibility mode (-compat[=4]), a class is passed indirectly if it has any one of the following:

  • A user-defined constructor
  • A virtual function
  • A virtual base class
  • A base that is passed indirectly
  • A non-static data member that is passed indirectly

Otherwise, the class is passed directly.

In standard mode (the default mode), a class is passed indirectly if it has any one of the following:

  • A user-defined copy constructor
  • A user-defined destructor
  • A base that is passed indirectly
  • A non-static data member that is passed indirectly

Otherwise, the class is passed directly.

8.4.1 Choosing to Pass Classes Directly

To maximize the chance that a class will be passed directly:

  • Use default constructors, especially the default copy constructor, where possible.
  • Use the default destructor where possible. The default destructor is not virtual, therefore a class with a default destructor should generally not be a base class.
  • Avoid virtual functions and virtual bases.

8.4.2 Passing Classes Directly on Various Processors

Classes (and unions) that are passed directly by the C++ compiler are passed exactly as the C compiler would pass a struct (or union). However, C++ structs and unions are passed differently on different architectures.

TABLE 8-1   Passing of Structs and Unions by Architecture
Architecture Description
SPARC V7/V8 Structs and unions are passed and returned by allocating storage within the caller and passing a pointer to that storage. (That is, all structs and unions are passed by reference.)
SPARC V9 Structs with a size no greater than 16 bytes (32 bytes) are passed (returned) in registers. Unions and all other structs are passed and returned by allocating storage within the caller and passing a pointer to that storage. (That is, small structs are passed in registers; unions and large structs are passed by reference.) As a consequence, small value classes are passed as efficiently as primitive types.
IA platforms Structs and unions are passed by allocating space on the stack and copying the argument onto the stack. Structs and unions are returned by allocating a temporary object in the caller's frame and passing the address of the temporary object as an implicit first parameter.


8.5 Cache Member Variables

Accessing member variables is a common operation in C++ member functions.

The compiler must often load member variables from memory through the this pointer. Because values are being loaded through a pointer, the compiler sometimes cannot determine when a second load must be performed or whether the value loaded before is still valid. In these cases, the compiler must choose the safe, but slow, approach and reload the member variable each time it is accessed.

You can avoid unnecessary memory reloads by explicitly caching the values of member variables in local variables, as follows:

  • Declare a local variable and initialize it with the value of the member variable.
  • Use the local variable in place of the member variable throughout the function.
  • If the local variable changes, assign the final value of the local variable to the member variable. However, this optimization may yield undesired results if the member function calls another member function on that object.

This optimization is most productive when the values can reside in registers, as is the case with primitive types. The optimization may also be productive for memory-based values because the reduced aliasing gives the compiler more opportunity to optimize.

This optimization may be counter-productive if the member variable is often passed by reference, either explicitly or implicitly.

On occasion, the desired semantics of a class requires explicit caching of member variables, for instance when there is a potential alias between the current object and one of the member function's arguments. For example:

complex& operator*= (complex& left, complex& right) { left.real = left.real * right.real + left.imag * right.imag; left.imag = left.real * right.imag + left.image * right.real; }

will yield unintended results when called with:


Page 5

This chapter explains how to build multithreaded programs. It also discusses the use of exceptions and explains how to share C++ Standard Library objects across threads.

For more information about multithreading, see the Multithreaded Programming Guide, the C++ Library Reference, the Tools.h++ User's Guide, and the Standard C++ Library User's Guide.

9.1 Building Multithreaded Programs

All libraries shipped with the C++ compiler are multithreading-safe. If you want to build a multithreaded application, or if you want to link your application to a multithreaded library, you must compile and link your program with the -mt option. This option passes -D_REENTRANT to the preprocessor and passes -lthread in the correct order to ld. For compatibility mode (-compat[=4]), the -mt option ensures that libthread is linked before libC. For standard mode (the default mode), the -mt option ensures that libthread is linked before libCrun.

Do not link your application directly with -lthread because this causes libthread to be linked in an incorrect order.

The following example shows the correct way to build a multithreaded application when the compilation and linking are done in separate steps:

example% CC -c -mt myprog.cc example% CC -mt myprog.o

The following example shows the wrong way to build a multithreaded application:

example% CC -c -mt myprog.o example% CC myprog.o -lthread <- libthread is linked incorrectly

9.1.1 Indicating Multithreaded Compilation

You can check whether an application is linked to libthread or not by using the ldd command:

example% CC -mt myprog.cc example% ldd a.out libm.so.1 =>      /usr/lib/libm.so.1 libCrun.so.1 =>   /usr/lib/libCrun.so.1 libw.so.1 =>      /usr/lib/libw.so.1 libthread.so.1 => /usr/lib/libthread.so.1 libc.so.1 =>      /usr/lib/libc.so.1 libdl.so.1 =>     /usr/lib/libdl.so.1

9.1.2 Using C++ Support Libraries With Threads and Signals

The C++ support libraries, libCrun, libiostream, libCstd, and libC are multithread safe but are not async safe. This means that in a multithreaded application, functions available in the support libraries should not be used in signal handlers. Doing so can result in a deadlock situation.

It is not safe to use the following in a signal handler in a multithreaded application:

  • Iostreams
  • new and delete expressions
  • Exceptions

9.2 Using Exceptions in a Multithreaded Program

The current exception-handling implementation is safe for multithreading; exceptions in one thread do not interfere with exceptions in other threads. However, you cannot use exceptions to communicate across threads; an exception thrown from one thread cannot be caught in another.

Each thread can set its own terminate() or unexpected() function. Calling set_terminate() or set_unexpected() in one thread affects only the exceptions in that thread. The default function for terminate() is abort() for the main thread, and thr_exit() for other threads (see Section 5.5 Specifying Runtime Errors).

Note – Thread cancellation (pthread_cancel(3T)) results in the destruction of automatic (local nonstatic) objects on the stack. When a thread is cancelled, the execution of local destructors is interleaved with the execution of cleanup routines that the user has registered with pthread_cleanup_push(). The local objects for functions called after a particular cleanup routine is registered are destroyed before that routine is executed.

9.3 Sharing C++ Standard Library Objects Between Threads

The C++ Standard Library (libCstd), which is multithread-safe, makes sure that the internals of the library work properly in a multithreading environment. You will still need to lock around any library objects that you yourself share between threads (except for iostreams and locale objects).

For example, if you instantiate a string, then create a new thread and pass that string to the thread by reference, then you must lock around write access to that string, since you are explicitly sharing the one string object between threads. (The facilities provided by the library to accomplish this task are described below.)

On the other hand, if you pass the string to the new thread by value, you do not need to worry about locking, even though the strings in the two different threads may be sharing a representation through Rogue Wave's "copy on write" technology. The library handles that locking automatically. You are only required to lock when making an object available to multiple threads explicitly, either by passing references between threads or by using global or static objects.

The following describes the locking (synchronization) mechanism used internally in the C++ Standard Library to ensure correct behavior in the presence of multiple threads.

The interface to this facility (including the names of files, macros, classes, and any class members) as well as the implementation are an internal detail of the library and are subject to change without notice. Backward compatibility is not guaranteed.

Two synchronization classes provide mechanisms for achieving multithreaded safety; _RWSTDMutex and _RWSTDGuard.

The _RWSTDMutex class provides a platform-independent locking mechanism through the following member functions:

  • void acquire()--Acquires a lock on self, or blocks until such a lock can be obtained.
  • void release()--Releases a lock on self.

    class _RWSTDMutex { public:     _RWSTDMutex ();     ~_RWSTDMutex ();     void acquire ();     void release (); };

The _RWSTDGuard class is a convenience wrapper class that encapsulates an object of _RWSTDMutex class. An _RWSTDGuard object attempts to acquire the encapsulated mutex in its constructor (throwing an exception of type ::thread_error, derived from std::exception on error), and releases the mutex in its destructor (the destructor never throws an exception).

class _RWSTDGuard { public:     _RWSTDGuard (_RWSTDMutex&);     ~_RWSTDGuard (); };

Additionally, you can use the macro _RWSTD_MT_GUARD(mutex) (formerly _STDGUARD) to conditionally create an object of the _RWSTDGuard class in multithread builds. The object guards the remainder of the code block in which it is defined from being executed by multiple threads simultaneously. In single-threaded builds the macro expands into an empty expression.

The following example illustrates the use of these mechanisms.

#include <rw/stdmutex.h>; // // An integer shared among multiple threads. // int I; // // A mutex used to synchronize updates to I. // _RWSTDMutex I_mutex; // // Increment I by one. Uses an _RWSTDMutex directly. // void increment_I () { I_mutex.acquire(); // Lock the mutex. I++; I_mutex.release(); // Unlock the mutex. } // // Decrement I by one. Uses an _RWSTDGuard. // void decrement_I () { _RWSTDGuard guard(I_mutex); // Acquire the lock on I_mutex. --I; // // The lock on I is released when destructor is called on guard. // }