📄 ch06.htm
字号:
<tt>void func(File& );</tt><tt>int main()</tt><tt>{</tt><tt> try //outer try</tt><tt> {</tt><tt> File f ("db.dat");</tt><tt> func; // 1</tt><tt> }</tt><tt>catch(...) // 7</tt><tt> //this handler will catch the re-thrown exception; </tt><tt> //note: the same exception type is required</tt><tt> {</tt><tt> cout<<"re-thrown exception caught";</tt><tt> }</tt><tt> return 0;</tt><tt>}</tt><tt>void func(File & f)</tt><tt>{</tt><tt> try //inner try</tt><tt> {</tt><tt> if (f.IsValid() == false )</tt><tt> throw FileException("db.dat"); // 2</tt><tt> }</tt><tt> catch(FileException &fe) // 3</tt><tt>//first chance to cope with the exception</tt><tt> {</tt><tt> cout<<"invalid file specification" <<fe.Error()<<endl;</tt><tt> if (f.OpenNew() != SUCCESS) (5)</tt><tt> //re-throw the original exception and let a higher handler deal with it</tt><tt> throw; // 6</tt><tt> }</tt><tt>}</tt></pre><p>In the preceding example, the function <tt>func()</tt> is called from the <tt>try</tt> block inside <tt>main()</tt> (1). The second <tt>try</tt> block inside <tt>func()</tt> throws an exception of type <tt>FileException</tt> (2). This exception is caught by the <tt>catch</tt> block inside <tt>func()</tt> (3). The <tt>catch</tt> block attempts to remedy the situation by opening a new file. This attempt fails (5), and the <tt>FileException</tt> is rethrown (6). Finally, the rethrown exception is caught -- this time, by the <tt>catch(...)</tt> block inside <tt>main()</tt> (7).</p><h3> <a name="Heading22">Function try Blocks</a></h3><p>A <i>function</i> <tt>try</tt><i> block</i> is a function whose body consists of a <tt>try</tt> block and its associated handlers. A function <tt>try</tt> block enables a handler to catch an exception </p><p>that is thrown during the execution of the <i>initializer expressions</i> in the constructor's member initialization list or during the execution of the constructor's body. Note, however, that unlike handlers of ordinary exceptions, the handler of a function <tt>try</tt> block merely catches the exception -- it cannot continue the object's construction normally. This is because the partially constructed object is destroyed as a result of the stack unwinding. In addition, the handler of a function <tt>try</tt> block cannot execute a <tt>return</tt> statement (eventually, the handler must exit by a <tt>throw</tt>). What is the use of a function <tt>try</tt> block then? The handler enables you to throw a different exception than the one that it just caught, thereby preventing a violation of the exception specification. For example</p><pre><tt>class X{}; </tt><tt>C::C(const std::string& s) throw (X) // allowed to throw X only </tt><tt>try</tt><tt>: str(s) // str's constructor might throw a bad_alloc exception, </tt><tt> // might violate C's exception specification</tt><tt>{</tt><tt> // constructor function body</tt><tt>}</tt><tt>catch (...) //handle any exception thrown from ctor initializer or ctor body</tt><tt>{</tt><tt> //... </tt><tt> throw X(); //replace bad_alloc exception with an exception of type X</tt><tt>}</tt></pre><p>In this example, a <tt>string</tt> object is first constructed as a member of class <tt>C</tt>. <tt>string</tt> might throw a <tt>bad_alloc</tt> exception during its construction. The function try block catches the <tt>bad_alloc</tt> exception and throws instead an exception of type <tt>X</tt>, which satisfies the exception specification of <tt>C</tt>'s constructor.</p><h3> <a name="Heading23">Use auto_ptr<> to Avoid Memory Leaks</a></h3><p>The Standard Library supplies the class template <tt>auto_ptr<></tt> (discussed in Chapter 10, "STL and Generic Programming"), which automatically deallocates memory that is allocated on the free store in much the same manner as local objects are reclaimed in case of exiting their scope. When an <tt>auto_ptr<></tt> is instantiated, it can be initialized with a pointer to an object that is allocated on the free store. When the current scope is exited, the destructor of the <tt>auto_ptr<></tt> object automatically deletes the object that is bound to it. By using <tt>auto_ptr<></tt>, you can avoid memory leakage in the case of an exception. Furthermore, <tt>auto_ptr<></tt> can simplify programming by sparing the bother of explicitly deleting objects that were allocated on the free store. <tt>auto_ptr<></tt> is defined in the standard <tt><memory></tt> header file. </p><p>For example</p><pre><tt>#include <memory></tt><tt>#include <iostream></tt><tt>using namespace std;</tt><tt>class Date{ public: const char * DateString(); };</tt><tt>void DisplayDate()</tt><tt>{</tt><tt> //create a local object of type auto_ptr<Date></tt><tt> auto_ptr<Date> pd (new Date); //now pd is owned by the template object</tt><tt> cout<< pd-> DateString();</tt><tt> //pd is automatically deleted by the destructor of auto_ptr; </tt><tt>}</tt></pre><p>In the preceding example, the <tt>auto_ptr<></tt> instance, <tt>pd</tt>, can be used like an ordinary pointer to <tt>Date</tt>. The overloaded operators <tt>*</tt>, <tt>-></tt>, and <tt>&</tt> of <tt>auto_ptr<></tt> provide the pointer-like syntax. <tt>pd</tt>'s<tt> bound object </tt> is automatically destroyed when <tt>DisplayDate()</tt> exits.</p><h2> <a name="Heading24"> Exception Handling Performance Overhead</a></h2><p>By nature, exception handling relies heavily on runtime type checking. When an exception is thrown, the implementation has to determine whether the exception was thrown from a <tt>try</tt> block (an exception can be thrown from a program section that is not enclosed within a <tt>try</tt> block -- by operator <tt>new</tt>, for example). If indeed the exception was thrown from a <tt>try</tt> block, the implementation compares the type of the exception and attempts to find a matching handler in the current scope. If a match is found, control is transferred to the handler's body. This is the optimistic scenario. What if the implementation cannot find a matching handler for the exception, though, or what if the exception was not thrown from a <tt>try</tt> block? In such a case, the current function is unwound from the stack and the next active function in the stack is entered. The same process is reiterated until a matching handler has been found (at that point, all the automatic objects that were created on the path from a <tt>try</tt> block to a <tt>throw</tt> expression have been destroyed). When no matching handler can be found in the program, <tt>terminate()</tt> is invoked and the program terminates.</p><h3> <a name="Heading25">Additional Runtime Type Information</a></h3><p>The exception handling mechanism has to store additional data about the type of every exception object and every <tt>catch</tt> statement in order to perform the runtime matching between an exception and its matching handler. Because an exception can be of any type, and because it can be polymorphic as well, its dynamic type must be queried at runtime, using <i>runtime type information</i> (<i>RTTI</i>). RTTI, imposes an additional overhead in terms of both execution speed and program size (see Chapter 7, "Runtime Type Information"). Yet RTTI alone is not enough. The implementation also requires runtime <i>code</i> information, that is, information about the structure of each function. This information is needed to determine whether an exception was thrown from a <tt>try</tt> block. This information is generated by the compiler in the following way: The compiler divides each function body into three parts: one that is outside a <tt>try</tt> block with no active objects, a second part that is also outside a <tt>try</tt> block but that has active objects that have to be destroyed during stack unwinding, and a third part that is within a <tt>try</tt> block.</p><h3> <a name="Heading26">Toggling Exception Handling Support</a></h3><p>The technicalities of exception handling implementation vary among compilers and platforms. In all of them, however, exception handling imposes additional overhead even when no exception is ever thrown. The overhead lies in both execution speed and program size. Some compilers enable you to toggle exception handling support. When it is turned off, the additional data structures, lookup tables, and auxiliary code are not generated. However, turning off exception handling is rarely an option. Even if you do not use exceptions directly, you are probably using them implicitly: Operator <tt>new</tt>, for example, might throw a <tt>std::bad_alloc</tt> exception when it fails -- and so do other built-in operators; STL containers might throw their own exceptions, and so might other functions of the Standard Library. Code libraries that are supplied by third party vendors might use exceptions as well. Therefore, you can safely turn off exception handling support only when you are porting pure C code into a C++ compiler. As long as pure C code is used, the additional exception handling overhead is unnecessary and can be avoided.</p><h2> <a name="Heading27">Misuses of Exception Handling</a></h2><p>Exception handling is not confined to errors. Some programmers might use it simply as an alternative control structure to <tt>for</tt> loops or <tt>while</tt> and <tt>do</tt> blocks. For example, a simple application that prompts the user to enter data until a certain condition has been fulfilled can be (rather naively) implemented as follows:</p><pre><tt>#include <iostream></tt><tt>using namespace std;</tt><tt>class Exit{}; //used as exception object</tt><tt>int main()</tt><tt>{</tt><tt> int num;</tt><tt> cout<< "enter a number; 99 to exit" <<endl;</tt><tt> try</tt><tt> {</tt><tt> while (true) //infinitely</tt><tt> {</tt><tt> cin>>num;</tt><tt> if (num == 99)</tt><tt> throw Exit(); //exit the loop</tt><tt> cout<< "you entered: " << num << "enter another number " <<endl;</tt><tt> }</tt><tt> }</tt><tt> catch (Exit& )</tt><tt> {</tt><tt> cout<< "game over" <<endl;</tt><tt> }</tt><tt> return 0;</tt><tt>}</tt></pre><p>In the preceding example, the programmer locates an infinite loop within a try block. The <tt>throw</tt> statement breaks the loop and transfers control to the following <tt>catch</tt> statement. This style of programming is not recommended, however. It is very inefficient due to the excess overhead of exception handling. Furthermore, it is rather verbose and might have been much simpler and shorter had it been written with a <tt>break</tt> statement. In demo apps such as this one, the difference is mostly a stylistic one. In large-scale applications, the use of exception handling as an alternative control structure imposes a significant performance overhead.</p><p>Simple runtime errors that can be handled safely and effectively without the heavy machinery of exception handling need to also be treated by traditional methods. For example, a password entry dialog box should not throw an exception if the user mistyped his or her password. It is much simpler to redisplay the password entry dialog again with an appropriate error message. On the other hand, if the user enters wrong passwords dozens of times in a row, this can indicate a malicious break-in attempt. In this case, an exception should be thrown. The appropriate handler can page the system administrator and security officer. </p><h2> <a name="Heading28">Conclusions</a></h2><p>The exception handling mechanism of C++ overcomes the problems associated with the traditional methods. It frees the programmer from writing tedious code that checks the success status of every function call. Exception handling also eliminates human mistakes. Another important advantage of exception handling is the automatic unwinding of the stack, which ensures that local active objects are properly destroyed and their resources are released.</p><p>Implementing an exception handling mechanism was not a trivial task. The need to query the dynamic type of exception led to the introduction of RTTI into C++. The additional overhead of exception handling derives from the RTTI data structures, the "scaffolding" code that is generated by the compiler, and other implementation-dependent factors. Exceptions can be grouped into categories; the standard exception classes are a good example of this. In recent years, a few loopholes in the exception handling mechanism have been fixed. The first was the addition of exception specifications to functions' prototypes. The second was the introduction of a function <tt>try</tt> block, which enables the program to handle an exception that is thrown during the execution of the initializer expressions in the constructor's member initialization list or during the execution of the constructor's body.</p><p>Exception handling is a very powerful and flexible tool for handling runtime errors effectively. However, use it judiciously.</p><CENTER><P><HR> <A HREF="/publishers/que/series/professional/0789720221/index.htm"><img src="/publishers/que/series/professional/0789720221/button/contents.gif" WIDTH="128"HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR><BR><BR><p></P><P>© <A HREF="/publishers/que/series/professional/0789720221/copy.htm">Copyright 1999</A>, Macmillan Computer Publishing. Allrights reserved.</p></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -