📄 mi11.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">
<HTML LANG="EN">
<HEAD>
<TITLE>More Effective C++ | Item 11: Prevent exceptions from leaving destructors</TITLE>
<LINK REL=STYLESHEET HREF=../INTRO/ECMEC.CSS>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/COOKIE.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">var imagemax = 0; setCurrentMax(0);</SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/DINGBATS.JS"></SCRIPT>
<SCRIPT>
var dingbase = "MI11_DIR.HTM";
var dingtext = "Item M11, P";
if (self == top) {
top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- SectionName = "M11: Prevent exceptions from leaving destructors" -->
<A NAME="39749"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MI10_FR.HTM" TARGET="_top">Item 10: Prevent resource leaks in constructors</A> <BR> Continue to <A HREF="./MI12_FR.HTM" TARGET="_top">Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function</A></FONT></DIV>
<P><A NAME="dingp1"></A><font ID="mititle">Item 11: Prevent exceptions from leaving destructors.</font><SCRIPT>create_link(1);</SCRIPT>
</P>
<A NAME="72127"></A><A NAME="38227"></A>
<P><A NAME="dingp2"></A>
There are two situations in which a destructor is called. The first is when an object is destroyed under "normal" conditions, e.g., when it goes out of scope or is explicitly <CODE>delete</CODE>d. The second is when an object is destroyed by the exception-handling mechanism during the stack-unwinding part of exception <NOBR>propagation.<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P><A NAME="38249"></A>
<A NAME="p59"></A><P><A NAME="dingp3"></A>
That being the case, an exception may or may not be active when a destructor is invoked. Regrettably, there is no way to distinguish between these conditions from inside a destructor.<a href="#79380"><sup>4</sup></A> As a result, you must write your destructors under the conservative assumption that an exception <I>is</I> active, because if control leaves a destructor due to an exception while another exception is active, C++ calls the <CODE>terminate</CODE> function. That function does just what its name suggests: it terminates execution of your program. Furthermore, it terminates it <I>immediately</I>; not even local objects are <NOBR>destroyed.<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P><A NAME="38335"></A>
<P><A NAME="dingp4"></A>
As an example, consider a <CODE>Session</CODE> class for monitoring on-line computer sessions, i.e., things that happen from the time you log in through the time you log out. Each <CODE>Session</CODE> object notes the date and time of its creation and <NOBR>destruction:<SCRIPT>create_link(4);</SCRIPT>
</NOBR></P>
<A NAME="38352"></A>
<UL><PRE>class Session {
public:
Session();
~Session();
...
<A NAME="57502"></A>
private:
static void logCreation(Session *objAddr);
static void logDestruction(Session *objAddr);
};
</PRE>
</UL><A NAME="38354"></A>
<P><A NAME="dingp5"></A>
The functions <CODE>logCreation</CODE> and <CODE>logDestruction</CODE> are used to record object creations and destructions, respectively. We might therefore expect that we could code <CODE>Session</CODE>'s destructor like <NOBR>this:<SCRIPT>create_link(5);</SCRIPT>
</NOBR></P>
<A NAME="38365"></A>
<UL><PRE>Session::~Session()
{
logDestruction(this);
}
</PRE>
</UL><A NAME="42224"></A>
<P><A NAME="dingp6"></A>
This looks fine, but consider what would happen if <CODE>logDestruction</CODE> throws an exception. The exception would not be caught in <CODE>Session</CODE>'s destructor, so it would be propagated to the caller of that destructor. But if the destructor was itself being called because some other exception had been thrown, the <CODE>terminate</CODE> function would automatically be invoked, and that would stop your program dead in its <NOBR>tracks.<SCRIPT>create_link(6);</SCRIPT>
</NOBR></P><A NAME="42234"></A>
<P><A NAME="dingp7"></A>
In many cases, this is not what you'll want to have happen. It may be unfortunate that the <CODE>Session</CODE> object's destruction can't be logged, it might even be a major inconvenience, but is it really so horrific a pros<A NAME="p60"></A>pect that the program can't continue running? If not, you'll have to prevent the exception thrown by <CODE>logDestruction</CODE> from propagating out of <CODE>Session</CODE>'s destructor. The only way to do that is by using <CODE>try</CODE> and <CODE>catch</CODE> blocks. A naive attempt might look like <NOBR>this,<SCRIPT>create_link(7);</SCRIPT>
</NOBR></P>
<A NAME="42258"></A>
<UL><PRE>Session::~Session()
{
try {
logDestruction(this);
}
catch (...) {
cerr << "Unable to log destruction of Session object "
<< "at address "
<< this
<< ".\n";
}
}
</PRE>
</UL><A NAME="66918"></A>
<P><A NAME="dingp8"></A>but this is probably no safer than our original code. If one of the calls to <CODE>operator<<</CODE> in the <CODE>catch</CODE> block results in an exception being thrown, we're back where we started, with an exception leaving the <CODE>Session</CODE> <NOBR>destructor.<SCRIPT>create_link(8);</SCRIPT>
</NOBR></P><A NAME="66923"></A>
<P><A NAME="dingp9"></A>
We could always put a <CODE>try</CODE> block inside the <CODE>catch</CODE> block, but that seems a bit extreme. Instead, we'll just forget about logging <CODE>Session</CODE> destructions if <CODE>logDestruction</CODE> throws an <NOBR>exception:<SCRIPT>create_link(9);</SCRIPT>
</NOBR></P>
<A NAME="66929"></A>
<UL><PRE>Session::~Session()
{
try {
logDestruction(this);
}
catch (...) { }
}
</PRE>
</UL><A NAME="66927"></A>
<P><A NAME="dingp10"></A>
The <CODE>catch</CODE> block appears to do nothing, but appearances can be deceiving. That block prevents exceptions thrown from <CODE>logDestruction</CODE> from propagating beyond <CODE>Session</CODE>'s destructor. That's all it needs to do. We can now rest easy knowing that if a <CODE>Session</CODE> object is destroyed as part of stack unwinding, <CODE>terminate</CODE> will not be <NOBR>called.<SCRIPT>create_link(10);</SCRIPT>
</NOBR></P><A NAME="50936"></A>
<P><A NAME="dingp11"></A>
There is a second reason why it's bad practice to allow exceptions to propagate out of destructors. If an exception is thrown from a destructor and is not caught there, that destructor won't run to completion. (It will stop at the point where the exception is thrown.) If the destructor doesn't run to completion, it won't do everything it's supposed to do. For example, consider a modified version of the <CODE>Session</CODE> class where the creation of a session starts a database transaction and the termination of a session ends that <NOBR>transaction:<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P>
<A NAME="66973"></A>
<UL><PRE><A NAME="p61"></A>
Session::Session() // to keep things simple,
{ // this ctor handles no
// exceptions
logCreation(this);
startTransaction(); // start DB transaction
}
<A NAME="50888"></A>
Session::~Session()
{
logDestruction(this);
endTransaction(); // end DB transaction
}
</PRE>
</UL><A NAME="50881"></A>
<P><A NAME="dingp12"></A>
Here, if <CODE>logDestruction</CODE> throws an exception, the transaction started in the <CODE>Session</CODE> constructor will never be ended. In this case, we might be able to reorder the function calls in <CODE>Session</CODE>'s destructor to eliminate the problem, but if <CODE>endTransaction</CODE> might throw an exception, we've no choice but to revert to <CODE>try</CODE> and <CODE>catch</CODE> <NOBR>blocks.<SCRIPT>create_link(12);</SCRIPT>
</NOBR></P><A NAME="50973"></A>
<P><A NAME="dingp13"></A>
We thus find ourselves with two good reasons for keeping exceptions from propagating out of destructors. First, it prevents <CODE>terminate</CODE> from being called during the stack-unwinding part of exception propagation. Second, it helps ensure that destructors always accomplish everything they are supposed to accomplish. Each argument is convincing in its own right, but together, the case is ironclad. (If you're <I>still</I> not convinced, turn to <A HREF="../MAGAZINE/SU_FRAME.HTM" TARGET="_top">Herb Sutter's article</A>; in particular, to the section entitled, <A HREF="../MAGAZINE/SU_FRAME.HTM#destruct" TARGET="_top">"Destructors That Throw and Why They're Evil</A>.)<SCRIPT>create_link(13);</SCRIPT>
</P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MI10_FR.HTM" TARGET="_top">Item 10: Prevent resource leaks in constructors</A> <BR> Continue to <A HREF="./MI12_FR.HTM" TARGET="_top">Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function</A></FONT></DIV>
<A NAME="79380"></A>
<HR WIDTH="100%">
< NAME="dingp1"></A>
<SUP>4</SUP> Now there is. In July 1995, the <NOBR><FONT COLOR="#FF0000" SIZE="-2"><B>°</B></FONT><A HREF="http://www.awl.com/cseng/cgi-bin/cdquery.pl?name=committee" onMouseOver="self.status='ISO/ANSI Standatdization Committee Home Page'; return true" onMouseOut="self.status=self.defaultStatus" target="_top">ISO/ANSI</NOBR> standardization committee for C++</A> added a function, <CODE>uncaught_exception</CODE>, that returns <CODE>true</CODE> if an exception is active and has not yet been caught.<SCRIPT>create_link(14);</SCRIPT>
<BR>
<A HREF="#38249">Return</A>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -