📄 index.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META NAME="Author" Content="Steph Mineart">
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1">
<TITLE>ANSI/ISO C++ Professional Programmer's Handbook - Chapter 6 - Exception Handling</TITLE>
<link rel="stylesheet" TYPE="text/css" href="/includes/stylesheets/ebooks.css">
</head>
<BODY TEXT="#000000" BGCOLOR="#FFFFFF">
<CENTER>
<H1><img src="/publishers/que/series/professional/0789720221/button/que.gif" WIDTH="171" HEIGHT="66" ALIGN="BOTTOM" BORDER="0"><BR>
ANSI/ISO C++ Professional Programmer's Handbook</H1>
</CENTER>
<CENTER>
<P><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>
<HR>
</CENTER>
<H1 align="center">6</H1>
<h1 align="center"> Exception Handling </h1>
<address>by Danny Kalev</address>
<ul>
<li><a href="#Heading1">Introduction</a>
<li><a href="#Heading2">Traditional Error Handling Methods</a>
<ul>
<li><a href="#Heading3">Returning an Error Code</a>
<li><a href="#Heading4">Turning on a Global Flag</a>
<li><a href="#Heading5">Terminating the Program</a>
</ul>
<li><a href="#Heading6">Enter Exception Handling</a>
<ul>
<li><a href="#Heading7">The Challenges of Implementation of Exception Handling</a>
</ul>
<li><a href="#Heading8">Applying Exception Handling</a>
<ul>
<li><a href="#Heading9">Exception Handling Constituents</a>
<li><a href="#Heading10">Stack Unwinding</a>
<li><a href="#Heading11">Passing Exception Objects to a Handler</a>
<li><a href="#Heading12">Exception Type Match</a>
<li><a href="#Heading13">Exceptions as Objects</a>
<li><a href="#Heading14">Exception Specification</a>
</ul>
<li><a href="#Heading15">Exceptions During Object's Construction and Destruction</a>
<ul>
<li><a href="#Heading16">Throwing Exceptions From A Destructor is Dangerous</a>
</ul>
<li><a href="#Heading17">Global Objects: Construction and Destruction</a>
<li><a href="#Heading18">Advanced Exception Handling Techniques</a>
<ul>
<li><a href="#Heading19">Standard Exceptions</a>
<li><a href="#Heading20">Exception Handlers Hierarchy</a>
<li><a href="#Heading21">Rethrowing an Exception</a>
<li><a href="#Heading22">Function try Blocks</a>
<li><a href="#Heading23">Use auto_ptr<> to Avoid Memory Leaks</a>
</ul>
<li><a href="#Heading24"> Exception Handling Performance Overhead</a>
<ul>
<li><a href="#Heading25">Additional Runtime Type Information</a>
<li><a href="#Heading26">Toggling Exception Handling Support</a>
</ul>
<li><a href="#Heading27">Misuses of Exception Handling</a>
<li><a href="#Heading28">Conclusions</a>
</ul>
<hr size=4>
<h2> <a name="Heading1">Introduction</a></h2>
<p>Large software applications are built in layers. At the lowest level, you usually
find library routines, API functions, and proprietary infrastructure functions.
At the highest level, there are user interface components that enable a user
to, for instance, fill out a data sheet in a spreadsheet application. Consider
an ordinary flight-booking application: its topmost layer consists of GUI components
that display contents on the user's screen. These high-level components interact
with data access objects, which in turn encapsulate database API routines. At
a lower level, the database API routines interact with the database engine.
The database engine itself invokes system services that deal with low-level
hardware resources such as physical memory, file system, and security modules.
In general, severe runtime errors are detected in these lower code layers, which
cannot -- or should not -- attempt to handle these errors on their own. The
handling of severe runtime errors is the responsibility of higher-level components.
In order to handle an error, however, higher-level components have to be informed
that an error has occurred. Essentially, error handling consists of detecting
an error and notifying the software components that are in charge. These components
in turn handle the error and attempt to recover from it.</p>
<h2> <a name="Heading2">Traditional Error Handling Methods</a></h2>
<p>In its earlier stages, C++ did not have a built-in facility for handling runtime
errors. Instead, the traditional C methods were used for that purpose. These
methods can be grouped into three design policies:</p>
<ul>
<li>
<p> Return a status code with agreed-upon values to indicate either success
or failure.</p>
</li>
<p></p>
<li>
<p> Assign an error code to a global variable and have other functions examine
it.</p>
</li>
<p></p>
<li>
<p> Terminate the program altogether.</p>
</li>
</ul>
<p></p>
<p>Each of these methods has significant drawbacks and limitations in an object-oriented
environment. Some of them might be totally unacceptable, particularly in large-scale
applications. The following sections examine each of these methods more closely
in order to assess their inherent limitations and hazards.</p>
<h3> <a name="Heading3">Returning an Error Code</a></h3>
<p>To some extent, this method can be useful in small programs in which an agreed-upon,
closed set of error codes exists, and in which a rigorous policy of reporting
errors and checking the returned status of a function is applied. However, this
method has some noticeable limitations; for example, neither the error types
nor their enumerated values are standardized. Thus, in one library the implementer
might choose a return value of <tt>0</tt> (meaning <i>false</i>, perhaps) to
indicate an error, whereas another vendor might choose <tt>0</tt> to indicate
success and any nonzero value to indicate an error condition. Usually, the return
codes are shared in a common header file in the form of symbolic constants so
that some commonality can be maintained throughout an application or a development
team. These codes are not standardized, however.</p>
<p>Needless to say, the process of combining noncompatible software libraries
from different vendors or programmers becomes very difficult and confusing when
conflicting error codes are used. Another disadvantage is that every returned
code has to be looked up and interpreted -- a tedious and costly operation.
This policy requires that the return value of every function be checked every
time by every caller; failing to do so might lead to runtime disasters. When
an error code is detected, a return statement disrupts the normal execution
flow and passes the error code on to the caller. The additional code that wraps
every function call (to examine the return status and decide whether to continue
normally) can easily double the size of the program and cause serious difficulties
in the software's maintenance and readability. Worse yet, returning an error
value is sometimes impossible. For instance, constructors do not return values,
so they cannot use this method to report the failed construction of an object.
</p>
<h3> <a name="Heading4">Turning on a Global Flag</a></h3>
<p>An alternative approach for reporting runtime errors is to use global flags,
which indicate whether the last operation ended successfully. Unlike the return
code policy, this method is standardized. The C <errno.h> header file
defines a mechanism for examining and assigning the value of a global integer
flag, <tt>errno</tt>. Note that the inherent drawbacks of this policy are not
negligible. In a multithreaded environment, the error code that is assigned
to <tt>errno</tt> by one thread can be inadvertently overwritten by another
thread before the caller has had a chance to examine it. In addition, the use
of an error code instead of a more readable message is disadvantageous because
the codes might not be compatible among different environments. Finally, this
method requires a well-disciplined programming style that relies on constant
checking of the current value of <tt>errno</tt>. </p>
<p>The global flag policy is similar to the function return value policy: Both
provide a mechanism for reporting an error, but neither guarantees that the
error is actually handled. For example, a function that fails to open a file
can indicate a failure by assigning an appropriate value to <tt>errno</tt>.
However, it cannot prevent another function from attempting to write into the
file or close it. Furthermore, if <tt>errno</tt> indicates an error and the
programmer detects and handles it as is expected, <tt>errno</tt> still has to
be reset explicitly. A programmer might forget to do so, thereby causing other
functions, which assume that the error has not been handled, to attempt to rectify
the problem -- with unpredictable results.</p>
<h3> <a name="Heading5">Terminating the Program</a></h3>
<p>The most drastic method of handling a runtime error is simply to terminate
the program immediately when a severe error has been detected. This solution
averts some of the drawbacks of the previous two methods; for example, there
is no need for repetitive examination of the status that is returned from every
function call, nor does the programmer have to assign a global flag, test its
value, and clear it in a repetitive and error-prone manner. The standard C library
has two functions that terminate a program: <tt>exit()</tt> and <tt>abort()</tt>.
<tt>exit()</tt> can be called to indicate successful termination of a program
(as the final statement in <tt>main()</tt>), or it can be called in the case
of a runtime error. Before returning control to the environment, <tt>exit()</tt>
first flushes open streams and closes open files. <tt>abort()</tt>, on the other
hand, indicates abnormal program termination. It terminates immediately, without
flushing streams or closing open files.</p>
<p>Critical applications cannot just halt abruptly on every occurrence of a runtime
error. It would be disastrous if a life support machine stopped functioning
just because its controller detected a division by zero; likewise, the embedded
computer that controls the automatic functions of a manned space shuttle should
not halt just because it temporarily loses communication with ground control.
Similarly, applications such as the billing system of a telephone company or
a banking application cannot break down altogether whenever a runtime exception
occurs. Robust, real world applications can -- and must -- do better than that.</p>
<p>Program termination is problematic even for applications, such as an operating
system, that are expected to abort in the case of serious runtime errors. A
function that detects the error usually does not have the necessary information
to estimate the severity of the error. A memory allocation function, for example,
cannot tell whether an allocation request has failed because the user is currently
using a debugger, a Web browser, a spreadsheet, and a word processor all at
once, or because the system has become unstable due to a severe hardware fault.
In the first scenario, the system can simply display a message, requesting that
the user close unnecessary applications. In the second scenario, a more drastic
measure might be required. Under this policy, however, the allocation function
simply aborts the program (the operating system kernel, in this case), regardless
of the severity of the error. This is hardly applicable in nontrivial applications.
Good system design has to ensure that runtime errors are detected and reported,
but it also has to ensure a minimal level of fault tolerance.</p>
<p>Terminating the program might be acceptable under extreme conditions or during
debugging phases. However, <tt>abort()</tt> and <tt>exit()</tt> are never to
be used in an object-oriented environment, even during debugging, because they
are unaware of the C++ object model.</p>
<h4> exit() and abort() Do Not Destroy Objects</h4>
<p>An object can hold resources that are acquired by the constructor or a member
function: memory allocated on the free store, file handles, communication ports,
database transaction locks, I/O devices, and so on. These resources have to
be properly released by the object that uses them when it's done. Usually, these
resources are released by the destructor. This design idiom is called <i>resource
initialization is acquisition</i> (this is discussed in greater detail in Chapter
5, "Object-Oriented Program and Design"). Local objects that are created on
the stack are destroyed automatically when their block or function exits. Neither
<tt>abort() nor exit()</tt>, however, invokes the destructors of local objects.
Therefore, an abrupt program termination caused by calling these functions can
cause irreversible damage: A database can be corrupted, files can be lost, and
valuable data can evaporate. For this reason, do not use either <tt>abort()</tt>
or <tt>exit()</tt> in an object-oriented environment.</p>
<h2> <a name="Heading6">Enter Exception Handling</a></h2>
<p>As you have observed, none of the traditional error handling methods of C are
adequate for C++; one of the goals of C++ was to enable better and safer large-scale
software development than is offered by C.</p>
<p>The designers of C++ were aware of the considerable difficulties resulting
from the lack of a proper error handling mechanism. They sought a solution that
was free from all the ailments of C's traditional error handling. The suggested
mechanism was based on the automatic transfer of control to the system when
an exception is triggered. The mechanism had to be simple, and it had to free
the programmer from the drudgery of constantly examining a global flag or the
returned value of a function. Additionally, it had to ensure that the code sections
that handle the exception are automatically informed when an exception occurs.
Finally, it had to ensure that when an exception is not handled locally, local
objects are properly destroyed and their resources are released before the exception
is propagated to a higher handler.</p>
<p>In 1989, after several years of research and a plethora of draft proposals,
exception handling was added to C++. C++ is not the first language to offer
structured runtime error handling support. Back in the 1960s, PL/1 offered a
built-in exception handling mechanism; Ada provided its own version of exception
handling in the early 1980s, as did several other languages. But none of these
exception handling models fit the C++ object model and program structure. Therefore,
the proposed exception handling for C++ was unique, and it has served as a model
for newer languages that have appeared since. </p>
<p>Implementing an exception handling mechanism turned out to be a real challenge.
The 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -