📄 sutter.htm
字号:
<A NAME="AUTO00102"></A>
<UL><PRE>int i(s.Pop());
int j;
j = s.Pop();</PRE>
</UL>
<P><A NAME="dingp47"></A>Note that above we talked about "the initial copy" (from <CODE>v_[vused_-1]</CODE>). That's because there is another copy to worry about in either of the above cases, namely the copy of the returned temporary into the destination. (For you experienced readers, yes, it's actually "zero or one copies" because the compiler is free to optimize away the second copy if the return value optimization applies (see <SCRIPT>sendmetoo(20,45310,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M20'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M20</A>). The point is that there can be a copy, so you have to be ready for it.) If that copy construction or copy assignment fails, then the <CODE>Stack</CODE> has completed its side effect (the top element has been popped off) but the popped value is now lost forever because it never reached its destination (oops). This is bad news. In effect, it means that any version of <CODE>Pop</CODE> that is written to return a temporary like this cannot be made completely exception-safe, because even though the function's implementation itself may look technically exception-safe, it forces clients of <CODE>Stack</CODE> to write exception-unsafe code. More generally, mutator functions should not return <CODE>T</CODE> objects by <NOBR>value.<SCRIPT>create_link(47);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp48"></A><A NAME="AUTO00022"></A>The bottom line — and it's significant — is this: Exception safety affects your class's design! In other words, you must design for exception safety from the outset, and exception safety is never "just an implementation detail." One alternative is to respecify <CODE>Pop</CODE> as <NOBR>follows:<SCRIPT>create_link(48);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00103"></A>
<UL><PRE>template<class T>
void Stack<T>::Pop( T& result ) {
if( vused_ == 0) {
throw "pop from empty stack";
} else {
result = v_[vused_-1];
--vused_;
}
}</PRE>
</UL>
<P><A NAME="dingp49"></A>A potentially tempting alternative is to simply change the original version to return <CODE>T&</CODE> instead of <CODE>T</CODE> (this would be a reference to the popped <CODE>T</CODE> object, since for the time being the popped object happens to still physically exist in your internal representation) and then the caller could still write exception-safe code. But this business of returning references to "I no longer consider it there" resources is just purely evil (see <SCRIPT>sendmetoo(23,6210,'E');</SCRIPT> ONMOUSEOVER = "self.status = 'Item E23'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E23</A>). If you change your implementation in the future, this may no longer be possible! Don't go <NOBR>there.<SCRIPT>create_link(49);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp50"></A><A NAME="AUTO00023"></A>The modified <CODE>Pop</CODE> ensures that the <CODE>Stack</CODE>'s state is not changed unless the copy safely arrives in the caller's hands. Another option (and preferable, in my opinion) is to separate the functions of "querying the topmost value" and "popping the topmost value off the stack." We do this by having one function for <NOBR>each:<SCRIPT>create_link(50);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00104"></A>
<UL><PRE>template<class T>
T& Stack<T>::Top() {
if( vused_ == 0) {
throw "empty stack";
}
return v_[vused_-1];
}
template<class T>
void Stack<T>::Pop() {
if( vused_ == 0) {
throw "pop from empty stack";
} else {
--vused_;
}
}</PRE>
</UL>
<P><A NAME="dingp51"></A>Incidentally, have you ever grumbled at the way the standard library containers' (see <SCRIPT>sendmetoo(49,8392,'E');</SCRIPT> ONMOUSEOVER = "self.status = 'Item E49'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E49</A>) pop functions (e.g., <CODE>list::pop_back</CODE>, <CODE>stack::pop</CODE>, etc.) don't return the popped value? Well, here's one reason to do this: It avoids weakening exception safety. In fact, you've probably noticed that the above separated <CODE>Top</CODE> and <CODE>Pop</CODE> now match the signatures of the top and <CODE>pop</CODE> members of the standard library's <CODE>stack<></CODE> adapter. That's no coincidence! We're actually only two public member functions away from the <CODE>stack<></CODE> adapter's full public interface, <NOBR>namely:<SCRIPT>create_link(51);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00105"></A>
<UL><PRE>template<class T>
const T& Stack<T>::Top() const {
if( vused_ == 0) {
throw "empty stack";
} else {
return v_[vused_-1];
}
}</PRE>
</UL>
<P><A NAME="dingp52"></A>to provide <CODE>Top</CODE> for const <CODE>Stack</CODE> objects, <NOBR>and:<SCRIPT>create_link(52);</SCRIPT>
</NOBR></P>
<A NAME="AUTO00106"></A>
<UL><PRE>template<class T>
bool Stack<T>::Empty() const {
return( vused_ == 0 );
}</PRE>
</UL>
<P><A NAME="dingp53"></A>Of course, the standard <CODE>stack<></CODE> is actually a container adapter that's implemented in terms of another container, but the public interface is the same and the rest is just an implementation <NOBR>detail.<SCRIPT>create_link(53);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp54"></A><FONT ID="aititle">Levels of Safety: The Basic and Strong Guarantees</FONT><SCRIPT>create_link(54);</SCRIPT>
</P>
<P><A NAME="dingp55"></A>Just as there's more than one way to skin a cat (somehow I have a feeling I'm going to get enraged email from animal lovers), there's more than one way to write exception-safe code. In fact, there are two main alternatives we can choose from when it comes to guaranteeing exception safety. These guarantees were first set out in this form by Dave <NOBR>Abrahams:<SCRIPT>create_link(55);</SCRIPT>
</NOBR></P>
<OL>
<A NAME="dingp56"></A><LI> <I>Basic Guarantee: Even in the presence of <CODE>T</CODE> or other exceptions, <CODE>Stack</CODE> objects don't leak resources.</I> Note that this also implies that the container will be destructible and usable even if an exception is thrown while performing some container operation. However, if an exception is thrown, the container will be in a consistent but not necessarily predictable state. Containers that support the basic guarantee can work safely in some settings. (This is similar to every <CODE>Stack</CODE> member function leaving the object in what Jack Reeves terms a good — but never a bad or an undefined — state. For details, consult Reeves' article, <A HREF="RE_FRAME.HTM"
ONMOUSEOVER = "self.status = 'Coping with Exceptions'; return true" ONMOUSEOUT = "self.status = self.defaultStatus"TARGET="_top">Coping with Exceptions</A>.)<SCRIPT>create_link(56);</SCRIPT>
<BR><BR>
<A NAME="AUTO00024"></A>
<A NAME="dingp57"></A><LI> <I>Strong Guarantee: If an operation terminates because of an exception, program state will remain unchanged.</I> This always implies commit-or-rollback semantics, including that no references or iterators into the container be invalidated if an operation fails. For example, if a <CODE>Stack</CODE> client calls <CODE>Top</CODE> and then attempts a <CODE>Push</CODE> which fails because of an exception, then the state of the <CODE>Stack</CODE> object must be unchanged, and the reference returned from the prior call to <CODE>Top</CODE> must still be valid. For more information on these guarantees, see Dave Abrahams' documentation of the <FONT COLOR="#FF0000" SIZE="-2"><B>°</B></FONT><A HREF="http://www.awl.com/cseng/cgi-bin/cdquery.pl?name=abrahams"
ONMOUSEOVER = "self.status = 'SGI exception-safe standard library adaptation'; return true" ONMOUSEOUT = "self.status = self.defaultStatus" TARGET="_top">SGI exception-safe standard library adaptation</A>.<SCRIPT>create_link(57);</SCRIPT>
</OL>
<P><A NAME="dingp58"></A><A NAME="AUTO00025"></A>Probably the most interesting point here is that when you implement the basic guarantee, the strong guarantee often comes along for free. (Note that I said "often," not "always." In the standard library, for example, <CODE>vector</CODE> is a well-known counterexample where satisfying the basic guarantee does not cause the strong guarantee to come along for free.) For example, in our <CODE>Stack</CODE> implementation, almost everything we did was needed to satisfy just the basic guarantee... and what's presented above very nearly satisfies the strong guarantee, with little or no extra work. Not half bad, considering all the trouble we went <NOBR>to.<SCRIPT>create_link(58);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp59"></A><A NAME="AUTO00026"></A>(There is one subtle way in which this version of <CODE>Stack</CODE> still falls short of the strong guarantee: If <CODE>Push()</CODE> is called and has to grow its internal buffer, but then its final <CODE>v_[vused_]</CODE> <CODE>=</CODE> <CODE>t;</CODE> assignment throws, the <CODE>Stack</CODE> is still in a consistent state and all, but its internal memory buffer has moved — which invalidates any previously valid references returned from <CODE>Top()</CODE>. This last flaw in <CODE>Stack::Push()</CODE> can be fixed fairly easily by moving some code and adding a <CODE>try </CODE> block. For a better solution, however, see the <CODE>Stack</CODE> presented below — that <CODE>Stack</CODE> does not have this problem, and it does satisfy the strong commit-or-rollback <NOBR>guarantee.)<SCRIPT>create_link(59);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp60"></A><FONT ID="aititle">Points to Ponder</FONT><SCRIPT>create_link(60);</SCRIPT>
</P>
<P><A NAME="dingp61"></A>Note that we've been able to implement <CODE>Stack</CODE> to be not only exception-safe but fully exception-neutral, yet we've used only a single <CODE>try/catch</CODE>. As we'll see below, using better encapsulation techniques can get rid of even this try block. That means we can write a fully exception-safe and exception-neutral generic container without using try or catch... which really is pretty <NOBR>cool.<SCRIPT>create_link(61);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp62"></A><A NAME="AUTO00027"></A>As originally defined, <CODE>Stack</CODE> requires its instantiation type to have <NOBR>a:<SCRIPT>create_link(62);</SCRIPT>
</NOBR></P>
<UL>
<A NAME="dingp63"></A><LI>default constructor (to construct the <CODE>v_</CODE> buffers)<SCRIPT>create_link(63);</SCRIPT>
<A NAME="AUTO00028"></A>
<A NAME="dingp64"></A><LI>copy constructor (if <CODE>Pop</CODE> returns by value)<SCRIPT>create_link(64);</SCRIPT>
<A NAME="AUTO00029"></A>
<A NAME="dingp65"></A><LI>nonthrowing destructor (to be able to guarantee exception safety)<SCRIPT>create_link(65);</SCRIPT>
<A NAME="AUTO00030"></A>
<A NAME="dingp66"></A><LI><EM>exception-safe</EM> copy assignment (to set the values in <CODE>v_</CODE>, and if the copy assignment throws then it must guarantee that the target object is unchanged; note that this is the only <CODE>T</CODE> member function which must be exception-safe in order for our <CODE>Stack</CODE> to be exception-safe)<SCRIPT>create_link(66);</SCRIPT>
</UL>
<P><A NAME="dingp67"></A><A NAME="AUTO00031"></A>Next, we'll see how to reduce even these requirements without compromising exception safety, and along the way we'll get an even more detailed look at the standard operation of the statement <CODE>delete[]</CODE> <CODE>x;</CODE>.<SCRIPT>create_link(67);</SCRIPT>
</P>
<P><A NAME="dingp68"></A><FONT ID="aititle">Delving Deeper</FONT><SCRIPT>create_link(68);</SCRIPT>
</P>
<P><A NAME="dingp69"></A>Now I'll delve a little deeper into the <CODE>Stack</CODE> example, and write not just one but two new-and-improved versions of the template. Not only is it possible to write exception-safe generic containers, but between the last approach and this one I'll have demonstrated no less than three different complete solutions to the exception-safe <CODE>Stack</CODE> <NOBR>problem.<SCRIPT>create_link(69);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp70"></A><A NAME="AUTO00032"></A>Along the way, I'll also answer several more interesting <NOBR>questions:<SCRIPT>create_link(70);</SCRIPT>
</NOBR></P>
<UL>
<A NAME="dingp71"></A><LI> How can we use more advanced techniques to simplify the way we manage resources, and get rid of the last <CODE>try/catch</CODE> into the bargain?<SCRIPT>create_link(71);</SCRIPT>
<A NAME="AUTO00033"></A>
<A NAME="dingp72"></A><LI> How can we improve <CODE>Stack</CODE> by reducing the requirements on <CODE>T</CODE>, the contained type?<SCRIPT>create_link(72);</SCRIPT>
<A NAME="AUTO00034"></A>
<A NAME="dingp73"></A><LI> Should generic containers use exception specifications?<SCRIPT>create_link(73);</SCRIPT>
<A NAME="AUTO00035"></A>
<A NAME="dingp74"></A><LI> What do <CODE>new[]</CODE> and <CODE>delete[]</CODE> really do?<SCRIPT>create_link(74);</SCRIPT>
</UL>
<P><A NAME="dingp75"></A><A NAME="AUTO00036"></A>The answer to the last may be quite different than you expect. Writing exception-safe containers in C++ isn't rocket science; it just requires significant care and a good understanding of how the language works. In particular, it helps to develop a habit of eyeing with mild suspicion anything that might turn out to be a function call — including user-defined operators, user-defined conversions (see <SCRIPT>sendmetoo(5,5970,'M');</SCRIPT> ONMOUSEOVER = "self.status = 'Item M5'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M5</A>), and silent temporary objects (see <SCRIPT>sendmetoo(19,41177,'M');</SCRIPT>
ONMOUSEOVER = "self.status = 'Item M19'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M19</A>) among the more subtle culprits — because any function call might throw (except for functions declared with an exception specification of <CODE>throw()</CODE>, or certain functions in the standard library that are documented to never <NOBR>throw).<SCRIPT>create_link(75);</SCRIPT>
</NOBR></P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -