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

📄 reeves.htm

📁 高效c++编程
💻 HTM
📖 第 1 页 / 共 5 页
字号:
    throw "pop on empty stack";  return v_[--top_];              //>x1}The return statement uses the copy constructor of the template class T. If T::T(const T throws an exception, it will again do so after top_ has been changed. The exception will indicate that pop() failed, but the internal state will have been changed as though it succeeded. While it might be argued that the stack is still in a good state in this case, it does violate our goal we want to leave top_ as it was when we entered the function. Since we can not decrement top_ after the return statement, we must catch the exception and reset the state (we will also throw a more appropriate exception for the Stack empty condition): if (top_ == 0)  throw domain_error("empty stack");try {  return v_[--top_];} catch (...) {  top_++;  throw;}The domain_error class is one of the Standard library exception classes (see Item M12). It is derived from logic_error, which is in turn derived from exception. As discussed in the second article of this series [Reference 3], library exceptions should derive from the standard exception classes. Class domain_error is a general purpose base class that indicates that the domain of the attempted operation is invalid. For this example, I chose to throw a domain_error object directly rather than derive a new, Stack specific, domain_error subclass.Goal II. If you can not leave the object in a same state it had when the function was entered, try to leave it in a good state. In other words, even if we lose the old state of the object, we would like to be able to reuse the object. This is often perfectly acceptable with the assignment operator since we expect to lose the old value of the object when we assign a new value to it anyway. Consider the Stack assignment operator (in its initial form): template<class T>Stack<T>operator=(const Stack<T>s){  if (== this) return *this;  delete)v_;                                     //>x1  v_ = new T[nelems_ = s.nelems_];               //>x2  for (top_ = 0; top_ < s.top_; top_++)    v_[top_] = s.v_[top_];                       //>x3We have several possible exception sites in this function. As before, I will ignore the possibility of exceptions from the destructors invoked in x1. Line x2 uses the default constructor for class T, and line x3 uses T's assignment operator. We can (and will) re-write this function to meet Goal I, but for now observe that if an exception propagates from line x2 we are in an undefined state (worse than 'bad'). The old array has been deleted, but an exception at x2 means we failed to allocate its replacement. Pointer v_ is left dangling and if we try to destroy the Stack object,delete will be invoked on this dangling pointer. If we do nothing else, we want to make sure that we can always destroy the object (Goal III which we will get to later). This shows just how easy it is for a propagating exception to leave an object so messed up that it can not even be safely destroyed.If we get past line x2 and hit an exception in line x3 we are in a more interesting position. The Stack object is now actually in a consistent state, but since we only have a partial copy done, the object is not valid. The exception propagating from the function will indicate that the assignment failed, but at this point, since we have deleted the old stack data, we can not restore the object to the state it had before the function was called. The only valid things that can be done with this stack object are to destroy it, or reassign a valid stack to it. I emphasize the word valid. This is a case where the internal state of the object appears good, and hence the typical operations could be invoked and would appear to work, but the meta-state (for lack of a better term) is inconsistent. In this case, the stack object does not represent a valid last-in-first-out ordering of the objects on the stack because it was truncated in the middle of the copy of a valid stack.As noted, we can fix the assignment so that it meets Goal I, but let us assume that we could not do that.Guideline 4. Either reinitialize the object, or mark it internally to indicate that it is no longer usable but might be recovered. If the Stack object can not be restored to its original state, we want to make sure that any further attempt to use the object will be rejected. The user might handle the exception, but not realize that the object is no longer valid. In the case of our Stack assignment operator, we can simply reinitialize the object to the empty state. In more complicated cases, we may want to leave the object in the state it was in until the user explicitly clears the condition. This makes sure the user is aware of the problem. Our Stack::operator=() now becomes: template<class T>Stack<T>operator=(const Stack<T>s){  if (== this) return *this;  auto_array_ptr::remove(v_);                //>x1  top_ = 0; nelems_ = 0;                     // reinitialize object  v_ = new T[s.nelems_];                     //>x2  for (size_t i = 0; i < s.top_; i++)    v_[i] = s.v_[i];                         //>x3  nelems_ = s.nelems_;  top_ = s.top_;  return *this;}Line x1 deletes the array through a temporary (auto_array_ptr::remove() is discussed below) to guarantee that the object can be destroyed in case of an exception. Line x2 is now safe, and any exception will leave the object in a valid empty state. Likewise, an exception at line x3. As noted above, we can rewrite this function to meet Goal I, but this is a good start.Goal III. If you can not leave the object in a "good" state, make sure the destructor will still work. While the ultimate goal is to leave an object in a good state (Goal I or II) it may not always be possible. As a last resort, we want to leave the object in such a state that it can be safely destroyed. Never forget that as an exception propagates it will unwind the stack frame of every function it propagates through. Many times this will invoke the destructor of the very object that threw the exception. One question that should always be asked when attempting to cope with exceptions is: "What will happen if this exception attempts to destroy this object?"As our original Stack::operator=() function showed, it is not difficult to leave the object in a state where even the destructor will not work. The most common cause of this is a dangling pointer.Guideline 5. Do not leave dangling pointers in your objects. Delete pointers through temporaries. In the Stack assignment function above, the simplest solution to the dangling pointer was to set v_ to null after the delete statement. Since I figure this is going to be such a common occurrence, I added a static function to my auto_ptr (and auto_array_ptr) class to facilitate this operation. Calling auto_ptr::remove(p) (auto_array_ptr::remove(p)) will delete pointer p through a temporary and leave p set to null. Even if an exception occurs during the destructor call, p will not be left dangling. If you use auto_ptr objects as members of your class instead of raw data pointers (see Item M10), the task becomes even easier. If ap is an auto_ptr object, then: delete ap.release();will set ap to null internally before returning the pointer so it can be deleted.Goal IV. Avoid resource leaks. The most obvious type of resource leak is a memory leak, but memory is not the only resource that can leak (I once had a program that tended to leak TCP/IP sockets). In one sense, a resource leak is just another example of a bad state. In this case though, the resource that is in the "bad" state is the one that has leaked, not the one that did the leaking. For this reason, I deal with it separately.There are three different instances where exceptions can cause resource leaks: in a constructor, in a destructor, and in a function (whether a member of a class or not). Let us look at constructors first.Constructors are a special case for the exception handling mechanism. When an exception propagates from a constructor, the partial object that has been constructed so far is destroyed. If necessary, any memory allocated from the free store for the object is also released. Note that the destructor for the object itself is not called only destructors for any completely constructed subobjects. If, during the construction, a resource (such as memory) is obtained directly and not as part of a subobject whose destructor will release the resource, then a resource leak can occur (again, see Item M10). For example: class T1 { ... };class T2 { ... };class X {  T1* p1_;  T2* p2_;public:  X();  ~X();};X::X(){  p1_ = new T1;  p2_ = new T2;            // exception causes leak}If an exception is thrown by T2() during the initialization of p2_, then the T2 object will be destroyed, and the memory obtained by new will be released, but not the pointer held in p1_. We have a memory leak.There are a couple of ways this can be dealt with. We could use a try block to catch the exception and attempt to release the memory, but if we have several resources allocated this way, then the nested try blocks can get tedious and error prone. An alternative is to make sure the pointers are initialized to null, and then delete them all in the catch block: X::X() try :  p1_(null), p2_(null){  p1_ = new T1;  p2_ = new T2;}catch (...) {  delete p2_;  delete p1_;           // reverse order

⌨️ 快捷键说明

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