⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch06.htm

📁 c++语言操作手册
💻 HTM
📖 第 1 页 / 共 4 页
字号:
<!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&lt;&gt; 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 &lt;errno.h&gt; 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   under cfront, and under any other compiler in general.</p>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -