📄 reeves.htm
字号:
<A NAME="dingp69"></A><LI> Delay the increment of the <CODE>top_</CODE> variable until after the new element is assigned to the stack buffer.<SCRIPT>create_link(69);</SCRIPT>
</OL>
<P><A NAME="dingp70"></A><A NAME="AUTO00060"></A>Class <CODE>auto_array_ptr</CODE> is similar to the <CODE>auto_ptr</CODE> template class defined in the utilities section of the C++ Standard Library (See Items <SCRIPT>sendmetoo(9,5292,'M');</SCRIPT>> 9</A> and <SCRIPT>sendmetoo(28,61766,'M');</SCRIPT>> 28</A>). An <CODE>auto_ptr</CODE> is an object that owns a pointer. The destructor for <CODE>auto_ptr</CODE> deletes the object pointed to. My definition of <CODE>auto_ptr</CODE> is shown in <A HREF="#list2" ONMOUSEOVER = "self.status = 'Listing 2'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Listing 2</A>. The header for <CODE>auto_array_ptr</CODE> is shown in <A HREF="#list3" ONMOUSEOVER = "self.status = 'Listing 3'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Listing 3</A> (its definition is almost exactly the same as for <CODE>auto_ptr</CODE> except that it calls <CODE>delete[]</CODE> on its pointer instead of <CODE>delete</CODE>). Using an <CODE>auto_array_ptr</CODE> object will solve our memory leak by automatically deleting the array if an exception occurs. When the new array has been initialized with copies of the elements from the old buffer (an exception prone task), the ownership of the new buffer is assigned to the stack object. Lastly, we re-write the assignment at <CODE>x4</CODE> so that we do not update the state of the object member <CODE>top_</CODE> until after the potentially dangerous assignment has completed. All this gives <NOBR>us:<SCRIPT>create_link(70);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00150"></A><UL><PRE>
template<class T>
Stack<T>::push(const T& element)
{
if (top_ == nelems_) {
size_t new_nelems = nelems_ * 2;
auto_array_ptr<T> new_buffer =
new T[new_nelems]; //>x1
for (int i = 0; i < top_; i++)
new_buffer[i] = v_[i]; //>x2
v_ = new_buffer.reset(v_); //>x3
nelems_ = new_nelems;
}
v_[top_] = element; //>x4
top_++;
}
</PRE>
</UL>
<P><A NAME="dingp71"></A><A NAME="AUTO00061"></A>The statement at line 3 swaps ownership of the old buffer and the new buffer. After the statement, the <CODE>auto_array_ptr</CODE> object owns the old buffer and the <CODE>Stack</CODE> object owns the new buffer (via its member <CODE>v_</CODE>). The <CODE>new_buffer</CODE> object will then delete the old buffer when it is destroyed at the end of the block. I do it this way so that if an exception occurs when the old buffer is deleted, the <CODE>stack</CODE> object will still be in a good <NOBR>state.<SCRIPT>create_link(71);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp72"></A><A NAME="AUTO00062"></A>Even this simple example illustrates a key difficulty in coping with exceptions. When writing a function, it is necessary to decide which operations might cause exceptions and which operations are exception-safe. Exception specifications (which are discussed in a later article [<A HREF="#ref4" ONMOUSEOVER = "self.status = 'Reference 4'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Reference 4</A>]) can help in this task, but they are not a complete answer (see <SCRIPT>sendmetoo(14,6011,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M14'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M14</A>). In particular, template programmers do not have access to the exception specifications of classes used to instantiate the template. (For a further discussion of this topic, consult Herb Sutter's article, <A HREF="SU_FRAME.HTM" TARGET="_top" ONMOUSEOVER = "self.status = 'Exception-Safe Generic Containers'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">"Exception-Safe Generic Containers"</A>). Once you get into the mind set of expecting exceptions, the problem often becomes trying to determine which statements are NOT possible sources of exceptions. The only operations that can safely be assumed to never throw exceptions are the basic operations on the built-in data types. In the example above, the call to <CODE>auto_array_ptr::reset()</CODE> is safe because all it does is swap two built-in pointer types through a <NOBR>temporary.<SCRIPT>create_link(72);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp73"></A><A NAME="AUTO00063"></A>The <CODE>auto_array_ptr</CODE> object automatically deals with the possible exceptions in the loop at line <CODE>x2</CODE>. An exception prior to line 3 will now propagate out of the function without causing either a bad state or a memory leak. That leaves only the possible exception from the assignment at line <CODE>x4</CODE> to be dealt with, which brings up <A HREF="./REEVES.HTM#guide3" ONMOUSEOVER = "self.status = 'Guideline 3'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Guideline 3</A>.<SCRIPT>create_link(73);</SCRIPT>
</P>
<P><A NAME="dingp74"></A><A NAME="AUTO00064"></A><A NAME="guide3"></A><I>Guideline 3. Avoid side effects in expressions that might propagate exceptions.</I> Side effects are a fact of life in C++. Some of them we can not see directly (and can not do anything about). Others we can see. In statement <CODE>x4</CODE>, whatever happens inside <CODE>T::operator=()</CODE> are of the former type, <CODE>top_++</CODE> is one of the latter. As it was originally written, an exception from <CODE>T::operator=()</CODE> was guaranteed to leave us in a bad state (at least — the state might have been even worse, i.e. undefined) because <CODE>top_</CODE> would have been incremented before <CODE>T::operator=()</CODE> was called. Since <CODE>T::operator=()</CODE> might throw an exception, we must avoid the side effect to <CODE>top_</CODE> by postponing the <NOBR>increment.<SCRIPT>create_link(74);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp75"></A><A NAME="AUTO00065"></A>Note that even with this change, we can not be sure what state the <CODE>Stack</CODE> object is in if an exception occurs at line <CODE>x4</CODE> — it depends upon what state <CODE>T::operator=()</CODE> leaves the object at <CODE>v_[top_]</CODE>. If an exception thrown by <CODE>T::operator=()</CODE> leaves the <CODE>T</CODE> object in a good state, then we can say that the <CODE>Stack</CODE> object itself is in a good state. If the assignment leaves the <CODE>T</CODE> object in a bad state, then we must consider the <CODE>Stack</CODE> object to also be in a bad state — we can not expect another call to <CODE>push()</CODE> to work if the stack buffer now contains a bad object. (In this case, even though the stack itself is in a bad state, we can still <CODE>pop()</CODE> those objects that currently exist on the stack. In fact, as long as we do not try to <CODE>push()</CODE> an object into the slot that now contains the bad <CODE>T</CODE> subobject, we can still use our stack as if it were good, even though it isn't. This is often the case in real software <NOBR>systems.)<SCRIPT>create_link(75);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp76"></A><A NAME="AUTO00066"></A>In the final case, if an exception leaves the <CODE>T</CODE> object in an undefined state, then we must consider the <CODE>Stack</CODE> object to be in an undefined state. This means that it probably will not be possible to destroy the stack. While there is no way to tell what state the object at <CODE>v_[top_]</CODE> is in if an exception occurs at line 4, we want to make sure that if class <CODE>T</CODE> adheres to <A HREF="./REEVES.HTM#goal1" ONMOUSEOVER = "self.status = 'Goal I'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Goal I</A>, then the stack object does also. For a slightly more complicated example consider the <CODE>Stack::pop()</CODE> function. In its original <NOBR>form:<SCRIPT>create_link(76);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00151"></A><UL><PRE>
template<class T>
T Stack<T>::pop()
{
if (top_ == 0)
throw "pop on empty stack";
return v_[--top_]; //>x1
}
</PRE>
</UL>
<P><A NAME="dingp77"></A><A NAME="AUTO00067"></A>The return statement uses the copy constructor of the template class <CODE>T</CODE>. If <CODE>T::T(const T&)</CODE> throws an exception, it will again do so after <CODE>top_</CODE> has been changed. The exception will indicate that <CODE>pop()</CODE> 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 <CODE>top_</CODE> as it was when we entered the function. Since we can not decrement <CODE>top_</CODE> after the return statement, we must catch the exception and reset the state (we will also throw a more appropriate exception for the <CODE>Stack</CODE> empty <NOBR>condition):<SCRIPT>create_link(77);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00152"></A><UL><PRE>
if (top_ == 0)
throw domain_error("empty stack");
try {
return v_[--top_];
} catch (...) {
top_++;
throw;
}
</PRE>
</UL>
<P><A NAME="dingp78"></A><A NAME="AUTO00068"></A>The <CODE>domain_error</CODE> class is one of the Standard library exception classes (see <SCRIPT>sendmetoo(12,76790,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M12'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M12</A>). It is derived from <CODE>logic_error</CODE>, which is in turn derived from <CODE>exception</CODE>. As discussed in the second article of this series [<A HREF="#ref3" ONMOUSEOVER = "self.status = 'Reference 3'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Reference 3</A>], library exceptions should derive from the standard exception classes. Class <CODE>domain_error</CODE> 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 <CODE>domain_error</CODE> object directly rather than derive a new, <CODE>Stack</CODE> specific, <CODE>domain_error</CODE> <NOBR>subclass.<SCRIPT>create_link(78);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp79"></A><A NAME="AUTO00069"></A><A NAME="goal2"></A><B>Goal II.</B> <B>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.</B> 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 <CODE>Stack</CODE> assignment operator (in its initial <NOBR>form):<SCRIPT>create_link(79);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00153"></A><UL><PRE>
template<class T>
Stack<T>& operator=(const Stack<T>& s)
{
if (&s == 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_]; //>x3
</PRE>
</UL>
<P><A NAME="dingp80"></A><A NAME="AUTO00070"></A>We have several possible exception sites in this function. As before, I will ignore the possibility of exceptions from the destructors invoked in <CODE>x1</CODE>. Line <CODE>x2</CODE> uses the default constructor for class <CODE>T</CODE>, and line <CODE>x3</CODE> uses <CODE>T</CODE>'s assignment operator. We can (and will) re-write this function to meet <A HREF="./REEVES.HTM#goal1" ONMOUSEOVER = "self.status = 'Goal I'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Goal I</A>, but for now observe that if an exception propagates from line <CODE>x2</CODE> we are in an undefined state (worse than 'bad'). The old array has been deleted, but an exception at <CODE>x2</CODE> means we failed to allocate its replacement. Pointer <CODE>v_</CODE> is left dangling and if we try to destroy the <CODE>Stack</CODE> 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 (<A HREF="./REEVES.HTM#goal3" ONMOUSEOVER = "self.status = 'Goal III'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Goal III</A> — 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 <NOBR>destroyed.<SCRIPT>create_link(80);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp81"></A><A NAME="AUTO00071"></A>If we get past line <CODE>x2</CODE> and hit an exception in line <CODE>x3</CODE> we are in a more interesting position. The <CODE>Stack</CODE> 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 <CODE>stack</CODE> data, we can not restore the object to the state it had before the function was called. The only <EM>valid</EM> things that can be done with this <CODE>stack</CODE> object are to destroy it, or reassign a valid <CODE>stack</CODE> to it. I emphasize the word <EM>valid</EM>. 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 <CODE>stack</CODE> 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 <NOBR>stack.<SCRIPT>create_link(81);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp82"></A><A NAME="AUTO00072"></A>As noted, we can fix the assignment so that it meets <A HREF="./REEVES.HTM#goal1" ONMOUSEOVER = "self.status = 'Goal I'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Goal I</A>, but let us assume that we could not do <NOBR>that.<SCRIPT>create_link(82);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp83"></A><A NAME="AUTO00073"></A><A NAME="guide4"></A><I>Guideline 4. Either reinitialize the object, or mark it internally to indicate that it is no longer usable but might be recovered.</I> If the <CODE>Stack</CODE> 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 <CODE>Stack</CODE> 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 <CODE>Stack::operator=()</CODE> now <NOBR>becomes:<SCRIPT>create_link(83);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00154"></A><UL><PRE>
template<class T>
Stack<T>& operator=(const Stack<T>& s)
{
if (&s == 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;
}
</PRE>
</UL>
<P><A NAME="dingp84"></A><A NAME="AUTO00074"></A>Line x1 deletes the array through a temporary (<CODE>auto_array_ptr::remove()</CODE> 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 <A HREF="./REEVES.HTM#goal1" ONMOUSEOVER = "self.status = 'Goal I'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Goal I</A>, but this is a good <NOBR>start.<SCRIPT>create_link(84);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp85"></A><A NAME="AUTO00075"></A><A NAME="goal3"></A><B>Goal III. </B><B> If you can not leave the object in a "good" state, make sure the destructor will still work.</B> While the ultimate goal is to leave an object in a good state (Goal <A HREF="./REEVES.HTM#goal1" ONMOUSEOVER = "self.status = 'Goal I'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">I</A> or <A HREF="./REEVES.HTM#goal2" ONMOUSEOVER = "self.status = 'Goal II'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">II</A>) 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 <NOBR>object?"<SCRIPT>create_link(85);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp86"></A><A NAME="AUTO00076"></A>As our original <CODE>Stack::operator=()</CODE> 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 <NOBR>pointer.<SCRIPT>create_link(86);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp87"></A><A NAME="AUTO00077"></A><A NAME="guide5"></A><I>Guideline 5. Do not leave dangling pointers in your objects. Delete pointers through temporaries.</I> In the <CODE>Stack</CODE> assignment function above, the simplest solution to the dangling pointer was to set <CODE>v_</CODE> to null after the <CODE>delete</CODE> statement. Since I figure this is going to be such a common occurrence, I added a static function to my <CODE>auto_ptr</CODE> (and <CODE>auto_array_ptr</CODE>) class to facilitate this operation. Calling <CODE>auto_ptr::remove(p)</CODE> (<CODE>auto_array_ptr::remove(p)</CODE>) will delete pointer <CODE>p</CODE> through a temporary and leave <CODE>p</CODE> set to null. Even if an exception occurs during the destructor call, <CODE>p</CODE> will not be left dangling. If you use <CODE>auto_ptr</CODE> objects as members of your class instead of raw data pointers (see <SCRIPT>sendmetoo(10,38223,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M10'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M10</A>), the task becomes even easier. If <CODE>ap</CODE> is an <CODE>auto_ptr</CODE> object, <NOBR>then:<SCRIPT>create_link(87);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00155"></A><UL><PRE>
delete ap.release();
</PRE>
</UL>
<P><A NAME="dingp88"></A><A NAME="AUTO00078"></A>will set <CODE>ap</CODE> to null internally before returning the pointer so it can be <NOBR>deleted.<SCRIPT>create_link(88);</SCRIPT>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -