📄 sutter.htm
字号:
<A NAME="AUTO00094"></A>
<UL><PRE>template<class T>
Stack<T>::~Stack() {
delete[] v_; // this can't throw }
</PRE>
</UL>
<P><A NAME="dingp28"></A>Why can't the <CODE>delete[]</CODE> call throw? Recall that this invokes <CODE>T::~T</CODE> for each object in the array, then calls <CODE>operator</CODE> <CODE>delete[]</CODE> to deallocate the memory (see <SCRIPT>sendmetoo(8,33985,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M8'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M8</A></NOBR>). Now, we know that the deallocation by <CODE>operator</CODE> <CODE>delete[]</CODE> may never throw, because its signature is always one of the <NOBR>following:<SCRIPT>create_link(28);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00095"></A>
<UL><PRE>void operator delete[]( void* ) throw();
void operator delete[]( void*, size_t ) throw();</PRE>
</UL>
<P><A NAME="dingp29"></A>Strictly speaking, this doesn't prevent someone from providing an overloaded <CODE>operator</CODE> <CODE>delete[]</CODE> that does throw, but any such overload would violate this clear intent and should be considered defective. Hence the only thing that could possibly throw is one of the <CODE>T::~T</CODE> calls, and we're arbitrarily going to have <CODE>Stack</CODE> require that <CODE>T::~T</CODE> may not throw. Why? To make a long story short, we just can't implement the <CODE>Stack</CODE> destructor with complete exception safety if <CODE>T::~T</CODE> can throw, that's why. However, requiring that <CODE>T::~T</CODE> may not throw isn't particularly onerous, because there are plenty of other reasons why destructors should never be allowed to throw at all. (Frankly, you won't go far wrong if you just habitually write <CODE>throw()</CODE> after the declaration of every destructor you ever write. Even if exception specifications cause expensive checks under your current compiler (see <SCRIPT>sendmetoo(15,40989,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M15'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M15</A>), at least write all your destructors as though they were specified as <CODE>throw()</CODE>... that is, never allow exceptions to leave destructors.) Any class whose destructor can throw is likely to cause you all sorts of other problems anyway sooner or later, and you can't even reliably <CODE>new[]</CODE> or <CODE>delete[]</CODE> an array of them. More on that <NOBR>later.<SCRIPT>create_link(29);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp30"></A><FONT ID="aititle">Copy Construction and Copy Assignment</FONT><SCRIPT>create_link(30);</SCRIPT>
</P>
<P><A NAME="dingp31"></A>The next few functions will use a common helper function, <CODE>NewCopy</CODE>, to manage allocating and growing memory. <CODE>NewCopy</CODE> takes a pointer to (<CODE>src</CODE>) and size of (<CODE>srcsize</CODE>) an existing <CODE>T</CODE> buffer, and returns a pointer to a new and possibly larger copy of the buffer, passing ownership of the new buffer to the caller. If exceptions are encountered, <CODE>NewCopy</CODE> correctly releases all temporary resources and propagates the exception in such a way that nothing is <NOBR>leaked.<SCRIPT>create_link(31);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00096"></A>
<UL><PRE>template<class T>
T* NewCopy( const T* src,
size_t srcsize,
size_t destsize ) {
assert( destsize >= srcsize );
T* dest = new T[destsize];
try {
copy( src, src+srcsize, dest ); // copy is part of the STL;
// see <SCRIPT>sendmetoo(35,5473,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M35'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M35</A>
} catch(...) {
delete[] dest; // this can't throw
throw; // rethrow original exception
}
return dest;
}</PRE>
</UL>
<P><A NAME="dingp32"></A>Let's analyze this one step at a <NOBR>time:<SCRIPT>create_link(32);</SCRIPT>
</NOBR></P>
<OL>
<A NAME="dingp33"></A><LI>In the <CODE>new</CODE> statement, the allocation might throw <CODE>bad_alloc</CODE> or the <CODE>T::T</CODE>'s may throw anything. In either case, nothing is allocated and we simply allow the exception to propagate. This is leak-free and exception-neutral.<SCRIPT>create_link(33);</SCRIPT>
<A NAME="AUTO00016"></A>
<A NAME="dingp34"></A><LI> Next, we assign all the existing values using <CODE>copy</CODE>, and <CODE>copy</CODE> invokes <CODE>T::operator=</CODE>. If any of the assignments fail, we catch the exception, free the allocated memory, and rethrow the original exception. This is again both leak-free and exception-neutral. However, there's an important subtlety here: <CODE>T::operator=</CODE> must guarantee that, if it does throw, then the assigned-to <CODE>T</CODE> object must be unchanged. (Later, I will show an improved version of <CODE>Stack</CODE> which does not rely on <CODE>T::operator=</CODE>.)<SCRIPT>create_link(34);</SCRIPT>
<A NAME="AUTO00017"></A>
<A NAME="dingp35"></A><LI>If the allocation and copy both succeeded, then we return the pointer to the new buffer and relinquish ownership (that is, the caller is responsible for the buffer from here on out). The return simply copies the pointer value, which cannot throw.<SCRIPT>create_link(35);</SCRIPT>
<A NAME="AUTO00018"></A>
</OL>
<P><A NAME="dingp36"></A>With <CODE>NewCopy</CODE> in hand, the <CODE>Stack</CODE> copy constructor is easy to <NOBR>write:<SCRIPT>create_link(36);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00097"></A>
<UL><PRE>template<class T>
Stack<T>::Stack( const Stack<T>& other )
: v_(NewCopy( other.v_,
other.vsize_,
other.vsize_ )),
vsize_(other.vsize_),
vused_(other.vused_)
{ }</PRE>
</UL>
<P><A NAME="dingp37"></A>The only possible exception is from <CODE>NewCopy</CODE>, which manages its own resources. Next, we tackle copy <NOBR>assignment:<SCRIPT>create_link(37);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00098"></A>
<UL><PRE>template<class T>
Stack<T>&
Stack<T>::operator=( const Stack<T>& other ) {
if( this != &other ) {
T* v_new = NewCopy( other.v_,
other.vsize_,
other.vsize_ );
delete[] v_; // this can't throw
v_ = v_new; // take ownership
vsize_ = other.vsize_;
vused_ = other.vused_;
}
return *this; // safe, no copy involved
}</PRE>
</UL>
<P><A NAME="dingp38"></A>Again, after the routine weak guard against self-assignment (see <SCRIPT>sendmetoo(17,2264,'E');</SCRIPT> ONMOUSEOVER = "self.status = 'Item E17'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E17</A></NOBR>), only the <CODE>NewCopy</CODE> call might throw; if it does, we correctly propagate that exception without affecting the Stack object's state. To the caller, if the assignment throws then the state is unchanged, and if the assignment doesn't throw then the assignment and all of its side effects are successful and <NOBR>complete.<SCRIPT>create_link(38);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp39"></A><FONT ID="aititle"><CODE>Size()</CODE>, <CODE>Push()</CODE>, and <CODE>Pop()</CODE></FONT><SCRIPT>create_link(39);</SCRIPT>
</P>
<P><A NAME="dingp40"></A>The easiest of all <CODE>Stack</CODE>'s members to implement safely is <CODE>Size</CODE>, because all it does is copy a built-in which can never <NOBR>throw:<SCRIPT>create_link(40);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00099"></A>
<UL><PRE>template<class T>
size_t Stack<T>::Size() const {
return vused_; // safe, builtins don't throw
}</PRE>
</UL>
<P><A NAME="dingp41"></A>However, with <CODE>Push</CODE> we need to apply our now-usual duty of <NOBR>care:<SCRIPT>create_link(41);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00100"></A>
<UL><PRE>template<class T>
void Stack<T>::Push( const T& t ) {
if( vused_ == vsize_ ) // grow if necessary
{ // by some grow factor
size_t vsize_new = vsize_*2+1;
T* v_new = NewCopy( v_, vsize_, vsize_new );
delete[] v_; // this can't throw
v_ = v_new; // take ownership
vsize_ = vsize_new;
}
v_[vused_] = t;
++vused_;
}</PRE>
</UL>
<P><A NAME="dingp42"></A>If we have no more space, we first pick a new size for the buffer and make a larger copy using <CODE>NewCopy</CODE>. Again, if <CODE>NewCopy</CODE> throws then our own <CODE>Stack</CODE>'s state is unchanged and the exception propagates through cleanly. Deleting the original buffer and taking ownership of the new one involves only operations that are known not to throw, so the entire <CODE>if</CODE> block is <NOBR>exception-safe.<SCRIPT>create_link(42);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp43"></A><A NAME="AUTO00019"></A>After any required grow operation, we attempt to copy the new value before incrementing our <CODE>vused_</CODE> count. This way, if the assignment throws, the increment is not performed and our <CODE>Stack</CODE>'s state is unchanged. If the assignment succeeds, the <CODE>Stack</CODE>'s state is changed to recognize the presence of the new value, and all is <NOBR>well.<SCRIPT>create_link(43);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp44"></A><A NAME="AUTO00020"></A>Only one function left... that wasn't so hard, was it? Well, don't get too happy just yet, because it turns out that <CODE>Pop</CODE> is the most problematic of these functions to write with complete exception safety. Our initial attempt might look something like <NOBR>this:<SCRIPT>create_link(44);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00101"></A>
<UL><PRE>template<class T>
T Stack<T>::Pop() {
if( vused_ == 0) {
throw "pop from empty stack";
} else {
T result = v_[vused_-1];
--vused_;
return result;
}
}</PRE>
</UL>
<P><A NAME="dingp45"></A>If the stack is empty, we throw an appropriate exception. Otherwise, we create a copy of the <CODE>T</CODE> object to be returned, update our state, and return the <CODE>T</CODE> object. If the initial copy from <CODE>v_[vused_-1]</CODE> fails, the exception is propagated and the state of the <CODE>Stack</CODE> is unchanged, which is what we want. If the initial copy succeeds, our state is updated and the <CODE>Stack</CODE> is in its new consistent state, which is also what we <NOBR>want.<SCRIPT>create_link(45);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp46"></A><A NAME="AUTO00021"></A>So this works, right? Well, kind of. There is a subtle flaw here that's completely outside the purview of <CODE>Stack::Pop</CODE>. Consider the following client <NOBR>code:<SCRIPT>create_link(46);</SCRIPT>
</NOBR></P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -