📄 ch06.htm
字号:
<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><tt>int f(); // no exception specification, f can throw any type of exception</tt><tt>void g(int j) throw() // g promises not to throw any exception at all</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -