📄 mi13.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 13: Catch exceptions by reference</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 = "MI13_DIR.HTM";
var dingtext = "Item M13, P";
if (self == top) {
top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- SectionName = "M13: Catch exceptions by reference" -->
<A NAME="38224"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back 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> <BR> Continue to <A HREF="./MI14_FR.HTM" TARGET="_top">Item 14: Use exception specifications judiciously</A></FONT></DIV>
<P><A NAME="dingp1"></A><font ID="mititle">Item 13: Catch exceptions by reference.</font><SCRIPT>create_link(1);</SCRIPT>
</P>
<A NAME="72136"></A><A NAME="44745"></A>
<P><A NAME="dingp2"></A>
When you write a <CODE>catch</CODE> clause, you must specify how exception objects are to be passed to that clause. You have three choices, just as when specifying how parameters should be passed to functions: by pointer, by value, or by <NOBR>reference.<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P><A NAME="68467"></A>
<P><A NAME="dingp3"></A>
Let us consider first catch by pointer. In theory, this should be the least inefficient way to implement the invariably slow process of moving an exception from <CODE>throw</CODE> site to <CODE>catch</CODE> clause (see <A HREF="./MI15_FR.HTM#40989" TARGET="_top">Item 15</A>). That's because throw by pointer is the only way of moving exception information without copying an object (see <A HREF="./MI12_FR.HTM#76790" TARGET="_top">Item 12</A>). For <NOBR>example:<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P>
<A NAME="68505"></A>
<UL><PRE>
class exception { ... }; // from the standard C++
// library exception
// hierarchy (see <A HREF="./MI12_FR.HTM#76790" TARGET="_top">Item 12</A>)
<A NAME="68515"></A>
void someFunction()
{
static exception ex; // exception object
<A NAME="68593"></A>
...
<A NAME="68594"></A>
<A NAME="p69"></A>
throw &ex; // throw a pointer to ex
<A NAME="68595"></A>
...
<A NAME="68596"></A>
}
<A NAME="68446"></A>
void doSomething()
{
try {
someFunction(); // may throw an exception*
}
catch (exception *ex) { // catches the exception*;
... // no object is copied
}
}
</PRE>
</UL><A NAME="68541"></A>
<P><A NAME="dingp4"></A>
This looks neat and tidy, but it's not quite as well-kept as it appears. For this to work, programmers must define exception objects in a way that guarantees the objects exist after control leaves the functions throwing pointers to them. Global and static objects work fine, but it's easy for programmers to forget the constraint. If they do, they typically end up writing code like <NOBR>this:<SCRIPT>create_link(4);</SCRIPT>
</NOBR></P>
<A NAME="68542"></A>
<UL><PRE>void someFunction()
{
exception ex; // local exception object;
// will be destroyed when
// this function's scope is
... // exited
<A NAME="68613"></A>
throw &ex; // throw a pointer to an
... // object that's about to
} // be destroyed
</PRE>
</UL><A NAME="68534"></A>
<P><A NAME="dingp5"></A>
This is worse than useless, because the <CODE>catch</CODE> clause handling this exception receives a pointer to an object that no longer <NOBR>exists.<SCRIPT>create_link(5);</SCRIPT>
</NOBR></P><A NAME="68617"></A>
<P><A NAME="dingp6"></A>
An alternative is to throw a pointer to a new heap <NOBR>object:<SCRIPT>create_link(6);</SCRIPT>
</NOBR></P>
<A NAME="68619"></A>
<UL><PRE>void someFunction()
{
...
throw new exception; // throw a pointer to a new heap-
... // based object (and hope that
} // operator new — see <A HREF="./MI8_FR.HTM#33985" TARGET="_top">Item 8</A> —
// doesn't itself throw an
// exception!)
</PRE>
</UL><A NAME="68629"></A>
<P><A NAME="dingp7"></A>
This avoids the I-just-caught-a-pointer-to-a-destroyed-object problem, but now authors of <CODE>catch</CODE> clauses confront a nasty question: should they delete the pointer they receive? If the exception object was allocated on the heap, they must, otherwise they suffer a resource leak. If <A NAME="p70"></A>the exception object wasn't allocated on the heap, they mustn't, otherwise they suffer undefined program behavior. What to <NOBR>do?<SCRIPT>create_link(7);</SCRIPT>
</NOBR></P><A NAME="68449"></A>
<P><A NAME="dingp8"></A>
It's impossible to know. Some clients might pass the address of a global or static object, others might pass the address of an exception on the heap. Catch by pointer thus gives rise to the Hamlet conundrum: to delete or not to delete? It's a question with no good answer. You're best off ducking <NOBR>it.<SCRIPT>create_link(8);</SCRIPT>
</NOBR></P><A NAME="68458"></A>
<P><A NAME="dingp9"></A>
Furthermore, catch-by-pointer runs contrary to the convention established by the language itself. The four standard exceptions — <CODE>bad_alloc</CODE> (thrown when <CODE>operator</CODE> <CODE>new</CODE> (see <A HREF="./MI8_FR.HTM#33985" TARGET="_top">Item 8</A>) can't satisfy a memory request), <CODE>bad_cast</CODE> (thrown when a <CODE>dynamic_cast</CODE> to a reference fails; see <A HREF="./MI2_FR.HTM#77216" TARGET="_top">Item 2</A>), <CODE>bad_typeid</CODE> (thrown when <CODE>dynamic_cast</CODE> is applied to a null pointer), and <CODE>bad_exception</CODE> (available for unexpected exceptions; see <A HREF="./MI14_FR.HTM#6011" TARGET="_top">Item 14</A>) — are all objects, not pointers to objects, so you have to catch them by value or by reference, <NOBR>anyway.<SCRIPT>create_link(9);</SCRIPT>
</NOBR></P><A NAME="44829"></A>
<P><A NAME="dingp10"></A>
Catch-by-value eliminates questions about exception deletion and works with the standard exception types. However, it requires that exception objects be copied <I>twice</I> each time they're thrown (see <A HREF="./MI12_FR.HTM#76790" TARGET="_top">Item 12</A>). It also gives rise to the specter of the <I>slicing problem</I>, whereby derived class exception objects caught as base class exceptions have their derivedness "sliced off." Such "sliced" objects <I>are </I>base class objects: they lack derived class data members, and when virtual functions are called on them, they resolve to virtual functions of the base class. (Exactly the same thing happens when an object is passed to a function by value — see <a href="../EC/EI22_FR.HTM#6133" TARGET="_top">Item E22</A>.) For example, consider an application employing an exception class hierarchy that extends the standard <NOBR>one:<SCRIPT>create_link(10);</SCRIPT>
</NOBR></P>
<A NAME="68784"></A>
<UL><PRE>
class exception { // as above, this is a
public: // standard exception class
<A NAME="68812"></A>
virtual const char * what() throw();
// returns a brief descrip.
... // of the exception (see
// <A HREF="./MI14_FR.HTM#6011" TARGET="_top">Item 14</A> for info about
}; // the "throw()" at the
// end of the declaration)
<A NAME="68746"></A>
class runtime_error: // also from the standard
public exception { ... }; // C++ exception hierarchy
<A NAME="68782"></A>
class Validation_error: // this is a class added by
public runtime_error { // a client
public:
virtual const char * what() throw();
// this is a redefinition
... // of the function declared
}; // in class exception above
<A NAME="68781"></A>
<A NAME="p71"></A>
void someFunction() // may throw a validation
{ // exception
...
<A NAME="7561"></A>
if (<i>a validation test fails</i>) {
throw Validation_error();
}
<A NAME="7562"></A>
...
<A NAME="7563"></A>
}
<A NAME="68766"></A>
void doSomething()
{
try {
someFunction(); // may throw a validation
} // exception
<A NAME="68845"></A>
catch (exception ex) { // catches all exceptions
// in or derived from
// the standard hierarchy
<A NAME="68860"></A>
<br><br>
cerr << ex.what(); // calls exception::what(),
... // never
} // Validation_error::what()
}
</PRE>
</UL><A NAME="44841"></A>
<P><A NAME="dingp11"></A>
The version of <CODE>what</CODE> that is called is that of the base class, even though the thrown exception is of type <CODE>Validation_error</CODE> and <CODE>Validation_error</CODE> redefines that virtual function. This kind of slicing behavior is almost never what you <NOBR>want.<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P><A NAME="68889"></A>
<P><A NAME="dingp12"></A>
That leaves only catch-by-reference. Catch-by-reference suffers from none of the problems we have discussed. Unlike catch-by-pointer, the question of object deletion fails to arise, and there is no difficulty in catching the standard exception types. Unlike catch-by-value, there is no slicing problem, and exception objects are copied only <NOBR>once.<SCRIPT>create_link(12);</SCRIPT>
</NOBR></P><A NAME="68905"></A>
<P><A NAME="dingp13"></A>
If we rewrite the last example using catch-by-reference, it looks like <NOBR>this:<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<A NAME="68914"></A>
<UL><PRE>
void someFunction() // nothing changes in this
{ // function
...
<A NAME="7564"></A>
if (<i>a validation test fails</i>) {
throw Validation_error();
}
<A NAME="7566"></A>
...
<A NAME="7565"></A>
}
<A NAME="68915"></A>
<A NAME="p72"></A>void doSomething()
{
try {
someFunction(); // no change here
}
catch (exception& ex) { // here we catch by reference
// instead of by value
<A NAME="68917"></A>
cerr << ex.what(); // now calls
// Validation_error::what(),
... // not exception::what()
}
}
</PRE>
</UL><A NAME="68912"></A>
<P><A NAME="dingp14"></A>
There is no change at the <CODE>throw</CODE> site, and the only change in the <CODE>catch</CODE> clause is the addition of an ampersand. This tiny modification makes a big difference, however, because virtual functions in the <CODE>catch</CODE> block now work as we expect: functions in <CODE>Validation_error</CODE> are invoked if they redefine those in <CODE>exception</CODE>.<SCRIPT>create_link(14);</SCRIPT>
</P><A NAME="68978"></A>
<P><A NAME="dingp15"></A>
What a happy confluence of events! If you catch by reference, you sidestep questions about object deletion that leave you damned if you do and damned if you don't; you avoid slicing exception objects; you retain the ability to catch standard exceptions; and you limit the number of times exception objects need to be copied. So what are you waiting for? Catch exceptions by <NOBR>reference!<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back 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> <BR> Continue to <A HREF="./MI14_FR.HTM" TARGET="_top">Item 14: Use exception specifications judiciously</A></FONT></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -