📄 index.html
字号:
under cfront, and under any other compiler in general.</p>
<h3> <a name="Heading7">The Challenges of Implementation of Exception Handling</a></h3>
<p>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. </p>
<p>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. </p>
<p>As an additional complication, the implementation must invoke the destructors
of all local objects that were constructed on the path from a <tt>try</tt> block
to a <tt>throw</tt> expression before control passes to the appropriate handler.
This process is called <i>stack</i> <i>unwinding</i> (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.</p>
<h2> <a name="Heading8">Applying Exception Handling</a></h2>
<p>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.</p>
<blockquote>
<hr>
<strong>CAUTION: </strong> 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.
<hr>
</blockquote>
<h3> <a name="Heading9">Exception Handling Constituents</a></h3>
<p>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 <tt>try</tt><i> block</i>, a sequence of one or more <i>handlers</i>
associated with a <tt>try</tt> block, a <tt>throw</tt><i> expression</i>, and
the exception itself. The <tt>try</tt> block contains code that might throw
an exception. For example</p>
<pre>
<tt>try </tt>
<tt>{</tt>
<tt> int * p = new int[1000000]; //may throw std::bad_alloc</tt>
<tt>}</tt>
</pre>
<p>A <tt>try</tt> block is followed by a sequence of one or more <tt>catch</tt>
statements, or handlers, each of which handles a different type of exception.
For example</p>
<pre>
<tt>try</tt>
<tt>{</tt>
<tt> int * p = new int[1000000]; //may throw std::bad_alloc</tt>
<tt> //...</tt>
<tt>}</tt>
<tt>catch(std::bad_alloc& ) </tt>
<tt>{</tt>
<tt>}</tt>
<tt>catch (std::bad_cast&)</tt>
<tt>{</tt>
<tt>}</tt>
</pre>
<p>A handler is invoked only by a <tt>throw</tt> expression that is executed in
the handler's <tt>try</tt> block or in functions that are called from the handler's
<tt>try</tt> block. A <tt>throw</tt> expression consists of the keyword <tt>throw</tt>
and an <cite>assignment expression</cite>. For example</p>
<pre>
<tt>try</tt>
<tt>{</tt>
<tt> throw 5; // 5 is assigned to n in the following catch statement</tt>
<tt>}</tt>
<tt>catch(int n) </tt>
<tt>{</tt>
<tt>}</tt>
</pre>
<p>A <tt>throw</tt><i> </i>expression is similar to a <tt>return</tt> statement.
An <i>empty throw</i> is a <tt>throw</tt> statement without an operand. For
example</p>
<pre>
<tt>throw; </tt>
</pre>
<p>An empty throw inside a handler indicates a <i>rethrow</i>, which is discussed
momentarily. Otherwise, if no exception is presently being handled, executing
an empty throw calls <tt>terminate()</tt>. </p>
<h3> <a name="Heading10">Stack Unwinding</a></h3>
<p>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, </p>
<p>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 <tt>try</tt> block to a <tt>throw</tt>
expression have been destroyed. In the absence of an appropriate handler, the
program terminates. Note, however, that C++ ensures proper destruction of local
objects only when the thrown exception is handled. Whether an uncaught exception
causes the destruction of local objects during stack unwinding is implementation-dependent.
To ensure that destructors of local objects are invoked in the case of an uncaught
exception, you can add a <tt>catch</tt> <tt>all</tt> statement in <tt>main()</tt>.
For example</p>
<pre>
<tt>int main()</tt>
<tt>{</tt>
<tt> try</tt>
<tt> {</tt>
<tt> //...</tt>
<tt> }</tt>
<tt> catch(std::exception& stdexc) // handle expected exceptions</tt>
<tt> {</tt>
<tt> //...</tt>
<tt> }</tt>
<tt> catch(...) // ensure proper cleanup in the case of an uncaught exception</tt>
<tt> {</tt>
<tt> }</tt>
<tt> return 0;</tt>
<tt>}</tt>
</pre>
<p>The stack unwinding process is very similar to a sequence of <tt>return</tt>
statements, each returning the same object to its caller. </p>
<h3> <a name="Heading11">Passing Exception Objects to a Handler</a></h3>
<p>An exception can be passed by value or by reference to its handler. The memory
for the exception that is being thrown is allocated in an unspecified way (but
it is not allocated on the free store). Some implementations use a dedicated
exception stack, on which exception objects are created. When an exception is
passed by reference, the handler receives a reference to the exception object
that is constructed on the exception stack. Passing an exception by reference
ensures its polymorphic behavior. Exceptions that are passed by value are constructed
on the stack frame of the caller. For example </p>
<pre>
<tt>#include <cstdio></tt>
<tt>class ExBase {/*...*/};</tt>
<tt>class FileEx: public ExBase {/*...*/};</tt>
<tt>void Write(FILE *pf)</tt>
<tt>{</tt>
<tt> if (pf == NULL) throw FileEx();</tt>
<tt> //... process pf normally</tt>
<tt>}</tt>
<tt>int main ()</tt>
<tt>{</tt>
<tt> try</tt>
<tt> {</tt>
<tt> Write(NULL); //will cause a FileEx exception to be thrown</tt>
<tt> }</tt>
<tt> catch(ExBase& exception) //catch ExBase or any object derived from it</tt>
<tt> {</tt>
<tt> //diagnostics and remedies }</tt>
<tt>}</tt>
</pre>
<p>Repeatedly copying objects that are passed by value is costly because the exception
object can be constructed and destroyed several times before a matching handler
has been found. However, it occurs only when an exception is thrown, which only
happens in abnormal and -- hopefully -- rare situations. Under these circumstances,
performance considerations are secondary (exception handling performance is
discussed at the end of this chapter) to maintaining an application's integrity.</p>
<h3> <a name="Heading12">Exception Type Match</a></h3>
<p>The type of an exception determines which handler can catch it. The matching
rules for exceptions are more restrictive than are the matching rules for function
overloading. Consider the following example:</p>
<pre>
<tt>try</tt>
<tt>{</tt>
<tt> throw int();</tt>
<tt>}</tt>
<tt>catch (unsigned int) //will not catch the exception from the previous try-block</tt>
<tt>{ </tt>
<tt>}</tt>
</pre>
<p>The thrown exception is of type <tt>int</tt>, whereas the handler expects an
<tt>unsigned int</tt>. The exception handling mechanism does not consider these
to be matching types; as a result, the thrown exception is not caught. The matching
rules for exceptions allow only a limited set of conversions: For an exception
<tt>E</tt> and a handler taking <tt>T</tt> or <tt>T&</tt>, the match is
valid under one of the following conditions: </p>
<ul>
<li>
<p> <tt>T</tt> and <tt>E</tt> are of the same type (<tt>const</tt> and <tt>volatile</tt>
specifiers are ignored)</p>
</li>
<p></p>
<li>
<p> <tt>T</tt> is an unambiguous public base class of . </p>
</li>
</ul>
<p></p>
<p>If <tt>E</tt> and <tt>T</tt> are pointers, the match is valid if <tt>E</tt>
and <tt>T</tt> are of the same type or if <tt>E</tt> points to an object that
is publicly and unambiguously derived from the class that is pointed to by <tt>T</tt>.
In addition, a handler of type <tt>array of T</tt> or <tt>function returning
T</tt> is transformed into <tt>pointer to T</tt> or <tt>pointer to function
returning T</tt>, respectively.</p>
<h3> <a name="Heading13">Exceptions as Objects</a></h3>
<p></p>
<p>As you have probably noticed, the traditional convention of returning an integer
as an error flag is problematic and unsatisfactory in OOP. The C++ exception
handling mechanism offers more flexibility, safety, and robustness. An exception
can be a fundamental type such as <tt>int</tt> or a <tt>char *</tt>. It can
be a full-fledged object as well, with data members and member functions. Such
an object can provide the exception handler with more options for recovery.
A clever exception object, for example, can have a member function that returns
a detailed verbal description of the error, instead of letting the handler to
look it up in a table or a file. It can have member functions that enable the
program to recover from the runtime error after the error has been handled properly.
Consider a logger class that appends new records to an existing log file: If
it fails to open the log file, it throws an exception. When it is caught by
the matching handler, the exception object can have a member function, which
creates a dialog box. The operator can choose recovery measures from the dialog
box, including creation of a new log file, redirecting the logger to an alternative
file, or simply allowing the system to run without a logger.</p>
<h3> <a name="Heading14">Exception Specification</a></h3>
<p>A function that might throw an exception can warn its users by specifying a
list of the exceptions that it can throw. Exception specifications are particularly
useful when users of a function can view its prototype but cannot access its
source file. Following is an example of specifying an exception:</p>
<pre>
<tt>class Zerodivide{/*..*/};</tt>
<tt>int divide (int, int) throw(Zerodivide); // function may throw an exception</tt>
<tt> // of type Zerodivide, but no other</tt>
</pre>
<p>If your function never throws any exceptions, it can be declared as follows:</p>
<pre>
<tt>bool equals (int, int) throw(); //no exception is thrown from this function</tt>
</pre>
<p>Note that a function that is declared without an exception specification such
as</p>
<pre>
<tt>bool equals (int, int);</tt>
</pre>
<h4> guarantees nothing about its exceptions: It might throw any exception, or
it might throw no exceptions. Exception Specifications Are Enforced At Runtime</h4>
<p>An exception specification may not be checked at compile time, </p>
<p>but rather at runtime. When a function attempts to throw an exception that
it is not allowed to throw according to its exception specification, the exception
handling mechanism detects the violation and invokes the standard function <tt>unexpected()</tt>.
The default behavior of <tt>unexpected()</tt> is to call <tt>terminate()</tt>,
which terminates the program. A violation of an exception specification is most
likely a bug, and should not occur -- this is why the default behavior is program
termination. The default behavior can be altered, nonetheless, by using the
function <tt>set_unexpected().</tt></p>
<p>Because exception specifications are enforced only at runtime, the compiler
might deliberately ignore code that seemingly violates exception specifications.
Consider the following:</p>
<pre>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -