📄 reeves.htm
字号:
When you start trying to deal with every possible exception that might occur, you can get into a situation where you either have to assume that an operation will work, or call terminate(). Consider what happens to our Stack if we try to make sure that an exception in push() always leaves the Stack in a good state. As we discussed under Guideline 3, if the assignment in the final step of push fails, and T::operator=() does not leave the element at v_[top_] in a good state, then our Stack is not in a good state. We might try to guarantee that the Stack is always left in a good state like this: try { v_[top_] = element; top++;} catch (...) { v_[top_].~T(); // destroy the 'bad' object new (T(); // create new 'good' object throw;}If the constructor in the catch clause throws an exception, then we have left our Stack object in an undefined state. After destroying the object at v_[top_] we can not even expect the destructor for Stack to work. We must assume that the constructor will be able to reinitialize the object at v_[top_].If you find yourself writing code like this, take it out. As we discussed in the beginning, exception handling is something that must pervade the entire program. At some point you have to turn the problem over to the user. Ultimately, only the application developer is in a position to really decide how to deal with certain errors. More often than not, the application will have to seek help from a human operator. In the final analysis, often the best we can do in handling an exception is to make sure our software stays together, and then propagate the exception and let a higher level deal with it.ConclusionsAs shown in this article, exceptions raise many issues that either were not there before, or which had much simpler solutions. Many of these Goals and Guidelines can be illustrated with simple examples, but are not simple to apply in real practice.The good thing about exceptions is that not every program needs to be truly fault tolerant in fact, very few do. What is much more important is that a program be robust. A robust program is resistant to errors it either works correctly, or it does not work at all; whereas a fault tolerant program must actually recover from errors. There are obviously different levels of "robustness." For years, I have liberally sprinkled my code with "assert" statements to make them more robust. Since all an assert statement usually does is print a message and abort the program, there are those who might question whether this actually qualifies as an improvement in robustness. Certainly, this is not acceptable behavior in a shipping application. For this reason, assert statements are usually used only for debugging and disabled in the final build of an application.Before exceptions, the only alternative to a deliberate abort was to return error flags and check them religiously. Exceptions provide a third possibility. If an exception is not handled at some point in a program, it will propagate out of function main() and invoke terminate(). Thus, an un-handled exception has much the same effect as an assert statement. Conversely, the program can choose to handle the exception (if only to provide a more graceful exit). For this reason alone, programmers are going to insist upon throwing exceptions, especially in library code it relieves them of the problem of deciding whether to abort the program, or not. As noted in the beginning, throwing exceptions is easy, coping with them is difficult.The simplest way to start out coping with exceptions is to have a single try/catch block in the main() function to provide a meaningful error message and a graceful exit of the program (Goal V applied to the entire program). A program written this way may not be any more robust than a properly written C program, but that level of robustness will have been obtained with a lot less effort (virtually none), and a lot less code. Just think of all the if statements that will not have to be written.From that point, developers can start to actually apply these guidelines to libraries and functions in order to bring them up to a point where they can be used in a fault tolerant program. We have to be careful not to rush into using catch clauses to handle exceptions or we run the risk of falling back into the problem where we think the program is running fine but in fact it has entered an erroneous state. Nevertheless, when used correctly, exceptions can definitely improve the reliability of our programs. As the examples in this paper have shown, this may take a lot of work, but developing reliable software usually does.References Tom Cargill, "Exception Handling: A False Sense of Security", C++ Report, Vol. 6, No. 9, November-December 1994. Harald M. Mller, "10 Rules for Handling Exception Handling Successfully," C++ Report, Vol. 8, No. 1, January 1996. Jack Reeves, "Exceptions and Standard C++", C++ Report, Vol. 8, No. 5, May 1996. Jack Reeves, "Ten Guidelines for Exception Specifications", C++ Report, Vol. 8, No. 7, July 1996. Scott Meyers, "How to Navigate the Treacherous Waters of C++ Exception Handling", Microsoft Systems Journal, Vol. 10, No. 11, November 1995. Jack Reeves, "Migrating from C to C++", C++ Report, Vol. 7, No. 7, July-August 1995. Listing 1: template<class T> class Stack { size_t nelems_; size_t top_; T* v_; public: size_t count() const { return top_; } void push(T); T pop(); Stack(); ~Stack(); Stack(const Stack Stackoperator=(const Stack };Listing 2: template<class X> class auto_ptr { X* p_; public: explicit auto_ptr(X* p = null) throw() : p_(p) {} auto_ptr(auto_ptr<X>ap) throw() : p_(ap.release()) {} ~auto_ptr() {delete p_;} void operator=(auto_ptr<X>rhs); Xoperator*() const throw() {return *p_;} X* operator->() const throw() {return p_;} X* get() const throw() {return p_;} X* release() throw() {return reset(null);} X* reset(X* p) throw() {X* tp = p_; p_ = p; return tp;} static void remove(X*x) {X* tp = x; x = null; delete tp;} }; template<class X> inline void auto_ptr<X>::operator=(auto_ptr<X>rhs) { if (this != { remove(p_); p_ = rhs.release(); } }Listing 3: template<class X> class auto_array_ptr { X* p_; public: auto_array_ptr(X* p = null) throw() : p_(p) {} auto_array_ptr(auto_array_ptr<X>ap) throw() : p_(ap.release()) {} ~auto_array_ptr() {delete[ ]p_;} void operator=(auto_array_ptr<X>rhs); Xoperator*() throw() {return *p_;} Xoperator[ ](int i) throw() {return p_[i];} X operator[ ](int i) const throw() {return p_[i];} X* get() const throw() {return p_;} X* release() throw() {return reset(null);} X* reset(X* p) throw() {X* tp = p_; p_ = p; return tp;} static void remove(X*x) {X* tp=x; x=null; delete[ ]tp;} };
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -