[ Pobierz całość w formacie PDF ]
first C++ compiler, cfront, ran on UNIX. Like many UNIX compilers, it was a translator
that first transformed C++ code into C, and then compiled the resultant C code. Release
4.0 of cfront was supposed to include exception handling. However, the implementation
of an exception handling mechanism that met all the requirements got so complicated that
the development team of cfront 4.0 decided to abandon the project entirely after spending
a whole year designing it. cfront 4.0 was never released; however, exception handling
became an integral part of Standard C++. Other compilers that started to appear later
supported it. The following section explains why it was it so difficult to implement
exception handling under cfront, and under any other compiler in general.
The Challenges of Implementation of Exception Handling
The difficulties in implementing exception handling arise from several factors. First, the
implementation must ensure that the proper handler for a specific exception is found.
Secondly, exception objects can be polymorphic; in that case, the implementation also
considers handlers of base classes when it cannot locate a matching handler for a derived
object.. This requirement implies a sort of runtime type checking to recover the dynamic
type of the exception object. Yet C++ did not have any runtime type checking facilities
whatsoever before exception handling was developed; these facilities had to be created
from scratch for that purpose.
As an additional complication, the implementation must invoke the destructors of all
local objects that were constructed on the path from a try block to a throw expression
before control passes to the appropriate handler. This process is called stack unwinding
(the stack unwinding process is discussed in further detail later in this chapter). Because
early C++ compilers translated the C++ source file into pure C and only then compiled
the code into machine code, the implementers of exception handling had to implement
runtime type identification and stack unwinding in C. Fortunately, these obstacles have
all been overcome.
Applying Exception Handling
Exception handling is a flexible and sophisticated tool. It overcomes the drawbacks of C's
traditional error handling methods and it can be used to handle a variety of runtime
errors. Still, exception handling, like other language features, can easily be misused. To
use this feature effectively, it is important to understand how the underlying runtime
machinery works and what the associated performance penalties are. The following
sections delve into exception handling internals and demonstrate how to use this tool to
create robust, bulletproof applications.
CAUTION: Some of the code samples in the following sections use new
exception handling features such as function try blocks and exception
specifications. Several compilers do not support these features yet;
therefore, it is recommended that you read the technical documentation of
your compiler to check whether it fully supports exception handling.
Exception Handling Constituents
Exception handling is a mechanism for transferring control from a point in a program
where an exception occurs to a matching handler. Exceptions are variables of built-in
data types or class objects. The exception handling mechanism consists of four
components: a try block, a sequence of one or more handlers associated with a try
block, a throw expression, and the exception itself. The try block contains code that
might throw an exception. For example
try
{
int * p = new int[1000000]; //may throw std::bad_alloc
}
A try block is followed by a sequence of one or more catch statements, or handlers,
each of which handles a different type of exception. For example
try
{
int * p = new int[1000000]; //may throw std::bad_alloc
//...
}
catch(std::bad_alloc& )
{
}
catch (std::bad_cast&)
{
}
A handler is invoked only by a throw expression that is executed in the handler's try
block or in functions that are called from the handler's try block. A throw expression
consists of the keyword throw and an assignment expression. For example
try
{
throw 5; // 5 is assigned to n in the following catch statement
}
catch(int n)
{
}
A throw expression is similar to a return statement. An empty throw is a throw
statement without an operand. For example
throw;
An empty throw inside a handler indicates a rethrow, which is discussed momentarily.
Otherwise, if no exception is presently being handled, executing an empty throw calls
terminate().
Stack Unwinding
When an exception is thrown, the runtime mechanism first searches for an appropriate
handler in the current scope. If such a handler does not exist,
the current scope is exited and the block that is higher in the calling chain is entered into
scope. This process is iterative: It continues until an appropriate handler has been found.
An exception is considered to be handled upon its entry to a handler. At this point, the
stack has been unwound and all the local objects that were constructed on the path from a
try block to a throw expression have been destroyed. In the absence of an appropriate
handler, the program terminates. Note, however, that C++ ensures proper destruction of
[ Pobierz całość w formacie PDF ]