📄 index.html
字号:
<tt> public: const char * Error() const { return s.c_str(); }</tt>
<tt> private: string s;</tt>
<tt>};</tt>
<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. All
rights reserved.</p>
</CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -