📄 ch06.htm
字号:
<tt>{</tt><tt> int result = f(); // if f throws an exception, g will violate its guarantee</tt><tt> //not to throw an exception. still, this code is legal</tt><tt>}</tt></pre><p>In this example, the function <tt>g()</tt>, which is not allowed to throw any exception, invokes the function <tt>f()</tt>. <tt>f()</tt>, however, is free to throw any exception because it has no exception specification. If <tt>f()</tt> throws an exception, it propagates through <tt>g()</tt>, thereby violating <tt>g()</tt>'s guarantee not to throw any exception.It might seem surprising that exception specifications are enforced only at runtime because at least some of the violations can be caught at compile time and flagged as errors. This is not the case, however. There are several compelling reasons for the runtime checking policy.. In the preceding example, <tt>f()</tt> can be a legacy C function. It is impossible to enforce every C function to have an exception specification. Forcing the programmer to write unnecessary <tt>try</tt> and <tt>catch(...)</tt> blocks in <tt>g()</tt> "just in case" is impractical as well -- what if the programmer knows that <tt>f()</tt> doesn't throw any exception at all and the code is therefore safe? By enforcing exception specifications at runtime, C++ applies the "trust the programmer" policy instead of forcing an unnecessary burden on both the programmer and the implementation.</p><h4> Concordance of Exception Specification</h4><p>C++ requires exception specification concordance in derived classes. This means that an overriding virtual function in a derived class has to have an exception specification that is at least as restrictive as the exception specification of the overridden function in the base class. For example</p><pre><tt>// various exception classes</tt><tt>class BaseEx{};</tt><tt>class DerivedEx: public BaseEx{};</tt><tt>class OtherEx {};</tt><tt>class A</tt><tt>{</tt><tt>public:</tt><tt> virtual void f() throw (BaseEx);</tt><tt> virtual void g() throw (BaseEx);</tt><tt> virtual void h() throw (DerivedEx);</tt><tt> virtual void i() throw (DerivedEx);</tt><tt> virtual void j() throw(BaseEx);</tt><tt>};</tt><tt>class D: public A</tt><tt>{</tt><tt>public: </tt><tt> void f() throw (DerivedEx); //OK, DerivedEx is derived from BaseEx</tt><tt>class D: public A</tt><tt>{</tt><tt>public: </tt><tt> void f() throw (DerivedEx); //OK, DerivedEx is derived from BaseEx</tt><tt> void g() throw (OtherEx); //error; exception specification is </tt><tt> //incompatible with A's</tt><tt> void h() throw (DerivedEx); //OK, identical to the exception </tt><tt> //specification in base</tt><tt> void i() throw (BaseEx); //error, BaseEx is not a DerivedEx nor is it</tt><tt> //derived from DerivedEx</tt><tt> void j() throw (BaseEx,OtherEx); //error, less restrictive than the</tt><tt> //specification of A::j</tt><tt>};</tt><tt>};</tt></pre><p>The same concordance restrictions apply to pointers to functions. A pointer to a function that has an exception specification can be assigned only to a function that has an identical or a more restrictive exception specification. This implies that a pointer to function that has no exception specification cannot be assigned to a function that has one. Note, however, that an exception specification is not considered part of the function type. Therefore, you cannot declare two distinct functions that differ only in their exception specification. For example</p><pre><tt>void f(int) throw (Y);</tt><tt>void f(int) throw (Z); //error; redefinition of 'void f(int)'</tt></pre><p>For the same reason, declaring a <tt>typedef</tt> that contains an exception specification is also an error:</p><pre><tt>typedef void (*PF) (int) throw(Exception); // error</tt></pre><h2> <a name="Heading15">Exceptions During Object's Construction and Destruction</a></h2><p>Constructors and destructors are invoked automatically; in addition, they cannot return values to indicate a runtime error. Seemingly, the most plausible way of reporting runtime errors during object construction and destruction is by throwing an exception. However, there are additional factors that you have to consider before throwing an exception in these cases. You should be particularly cautious about throwing an exception from a destructor.</p><h3> <a name="Heading16">Throwing Exceptions From A Destructor is Dangerous</a></h3><p>Throwing an exception from a destructor is not recommended. The problem is that a destructor might be invoked due to another exception as part of the stack unwinding. If a destructor that was invoked due to another exception also throws an exception of its own, the exception handling mechanism invokes <tt>terminate()</tt>. If you really have to throw an exception from a destructor, it is advisable to check first whether another uncaught exception is currently being processed. </p><h4> Checking for an Uncaught Exception</h4><p>A thrown exception is considered caught when its corresponding handler has been entered (or, if such a handler cannot be found, when the function <tt>unexpected()</tt> has been invoked). In order to check whether a thrown exception is currently being processed, you can use the standard function <tt>uncaught_exception()</tt> (which is defined in the standard header <stdexcept>). For example</p><pre><tt>class FileException{};</tt><tt>File::~File() throw (FileException)</tt><tt>{</tt><tt> if ( close(file_handle) != success) // failed to close current file?</tt><tt> {</tt><tt> if (uncaught_exception() == true ) // is there any uncaught exception </tt><tt> //being processed currently?</tt><tt> return; // if so, do not throw an exception</tt><tt> throw FileException(); // otherwise, it is safe to throw an exception</tt><tt> // to signal an error</tt><tt> }</tt><tt> return; // success</tt><tt>}</tt></pre><p>Still, a better design choice is to handle exceptions within a destructor rather than let them propagate into the program. For example</p><pre><tt>void cleanup() throw (int);</tt><tt>class C </tt><tt>{</tt><tt>public:</tt><tt> ~C();</tt><tt>};</tt><tt>C::~C() </tt><tt>{</tt><tt> try </tt><tt> {</tt><tt> cleanup();</tt><tt> }</tt><tt> catch(int) </tt><tt> {</tt><tt> //handle the exception within the destructor</tt><tt> }</tt><tt>}</tt></pre><p>If an exception is thrown by <tt>cleanup()</tt>, it is handled inside the destructor. Otherwise, the thrown exception will propagate outside the destructor, and if the destructor has been invoked while unwinding the stack due to another exception, <tt>terminate()</tt> will be called.</p><h2> <a name="Heading17">Global Objects: Construction and Destruction</a></h2><p>Conceptually, the construction of global objects takes place before program outset. Therefore, any exception that is thrown from a constructor of a global object can never be caught. This is also true for a global object's destructor -- the destruction of a global object executes after a program's termination. Hence, an exception that is thrown from a global object's destructor cannot be handled either. </p><h2> <a name="Heading18">Advanced Exception Handling Techniques</a></h2><p>The simple <tt>try</tt>-<tt>throw</tt>-<tt>catch</tt> model can be extended even further to handle more complicated runtime errors. This section discusses some of the more advanced uses of exception handling, including exception hierarchies, rethrowing exceptions, function <tt>try</tt> blocks and the <tt>auto_ptr</tt> class. </p><h3> <a name="Heading19">Standard Exceptions</a></h3><p>C++ defines a hierarchy of standard exceptions that are thrown at runtime when abnormal conditions arise. The standard exception classes are derived from <tt>std::exception</tt> (defined in the <stdexcept> header). This hierarchy enables the application to catch these exceptions in a single <tt>catch</tt> statement:</p><pre><tt>catch (std::exception& exc)</tt><tt>{</tt><tt> // handle exception of type std::exception as well as </tt><tt> //any exception derived from it</tt><tt>}</tt></pre><p>The standard exceptions that are thrown by built-in operators of the language are</p><pre><tt>std::bad_alloc //by operator new</tt><tt>std::bad_cast //by operator dynamic_cast < ></tt><tt>std::bad_typeid //by operator typeid</tt><tt>std::bad_exception //thrown when an exception specification of </tt></pre><p> //a function is violatedAll standard exceptions have provided the member function <tt>what()</tt>, which returns a <tt>const char *</tt> with an implementation-dependent verbal description of the exception. Note, however, that the standard library has an additional set of exceptions that are thrown by its components.</p><h3> <a name="Heading20">Exception Handlers Hierarchy</a></h3><p>Exceptions are caught in a bottom-down hierarchy: Specific (most derived classes) exceptions are handled first, followed by groups of exceptions (base classes), and, finally, a <tt>catch all</tt> handler. For example</p><pre><tt>#include <stdexcept> </tt><tt>#include <iostream></tt><tt>using namespace std;</tt><tt>int main()</tt><tt>{</tt><tt> try</tt><tt> {</tt><tt> char * buff = new char[100000000];</tt><tt> //...use buff</tt><tt> }</tt><tt> catch(bad_alloc& alloc_failure) // bad_alloc is </tt><tt> //derived from exception</tt><tt> {</tt><tt> cout<<"memory allocation failure";</tt><tt> //... handle exception thrown by operator new</tt><tt> }</tt><tt> catch(exception& std_ex) </tt><tt> {</tt><tt> cout<< std_ex.what() <<endl; </tt><tt> }</tt><tt> catch(...) // exceptions that are not handled elsewhere are caught here</tt><tt> {</tt><tt> cout<<"unrecognized exception"<<endl;</tt><tt> }</tt><tt> return 0;</tt><tt>}</tt></pre><p>Handlers of the most derived objects must appear before the handlers of base classes. This is because handlers are tried in order of appearance. It is therefore possible to write handlers that are never executed, for example, by placing a handler for a derived class after a handler for a corresponding base class. For example</p><pre><tt>catch(std::exception& std_ex) //bad_alloc exception is always handled here </tt><tt>{</tt><tt> //...handle the exception </tt><tt>}</tt><tt>catch(std::bad_alloc& alloc_failure) //unreachable </tt><tt>{</tt><tt> cout<<"memory allocation failure";</tt><tt>}</tt></pre><h3> <a name="Heading21">Rethrowing an Exception</a></h3><p>An exception is thrown to indicate an abnormal state. The first handle to catch the exception can try to fix the problem. If it fails to do so, or if it only manages to perform a partial recovery, it can still rethrow the exception, thereby letting a higher <tt>try</tt> block handle it. For that purpose, <tt>try</tt> blocks can be nested in a hierarchical order, enabling a rethrown exception from a lower <tt>catch</tt> statement to be caught again. A rethrow is indicated by a <tt>throw</tt> statement without an operand. For example</p><pre><tt>#include <iostream></tt><tt>#include <string></tt><tt>using namespace std;</tt><tt>enum {SUCCESS, FAILURE};</tt><tt>class File</tt><tt>{</tt><tt> public: File (const char *) {}</tt><tt> public: bool IsValid() const {return false; }</tt><tt> public: int OpenNew() const {return FAILURE; }</tt><tt>};</tt><tt>class Exception {/*..*/}; //general base class for exceptions</tt><tt>class FileException: public Exception</tt><tt>{</tt><tt> public: FileException(const char *p) : s(p) {}</tt><tt> public: const char * Error() const { return s.c_str(); }</tt><tt> private: string s;</tt><tt>};</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -