📄 ei23.htm
字号:
<A NAME="16998"></A>
<P><A NAME="dingp13"></A>
In fact, this is a guaranteed memory leak. Even if callers of <CODE>operator*</CODE> could be persuaded to take the address of the function's result and use <CODE>delete</CODE> on it (astronomically unlikely — <A HREF="./EI31_FR.HTM#6650" TARGET="_top">Item 31</A> shows what the code would have to look like), complicated expressions would yield unnamed temporaries that programmers would never be able to get at. For example, <NOBR>in<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<A NAME="6244"></A>
<UL><PRE>Rational w, x, y, z;
</PRE>
</UL><A NAME="6245"></A>
<UL><PRE>w = x * y * z;
</PRE>
</UL><A NAME="6246"></A>
<P><A NAME="dingp14"></A>
both calls to <CODE>operator*</CODE> yield unnamed temporaries that the programmer never sees, hence can never delete. (Again, see <A HREF="./EI31_FR.HTM#6650" TARGET="_top">Item 31</A>.)<SCRIPT>create_link(14);</SCRIPT>
</P>
<A NAME="17013"></A>
<P><A NAME="dingp15"></A>
But perhaps you think you're smarter than the average bear — or the average programmer. Perhaps you notice that both the on-the-stack and the on-the-heap approaches suffer from having to call a constructor for each result returned from <CODE>operator*</CODE>. Perhaps you recall that our initial goal was to avoid such constructor invocations. Perhaps you think you know of a way to avoid all but one constructor call. Perhaps the following implementation occurs to you, an implementation based on <CODE>operator*</CODE> returning a reference to a <I>static</I> <CODE>Rational</CODE> object, one defined <I>inside</I> the <NOBR>function:<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<A NAME="17036"></A>
<UL><PRE><A NAME="p104"></A>// the third wrong way to write this function
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
static Rational result; // static object to which a
// reference will be returned
</PRE>
</UL><A NAME="17039"></A>
<UL><PRE> <I>somehow multiply lhs and rhs and put the
resulting value inside result;</I>
</PRE>
</UL><A NAME="17043"></A>
<UL><PRE> return result;
}
</PRE>
</UL><A NAME="17028"></A>
<P><A NAME="dingp16"></A>
This looks promising, though when you try to compose real C++ for the italicized pseudocode above, you'll find that it's all but impossible to give <CODE>result</CODE> the correct value without invoking a <CODE>Rational</CODE> constructor, and avoiding such a call is the whole reason for this game. Let us posit that you manage to find a way, however, because no amount of cleverness can ultimately save this star-crossed <NOBR>design.<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<A NAME="17060"></A>
<P><A NAME="dingp17"></A>
To see why, consider this perfectly reasonable client <NOBR>code:<SCRIPT>create_link(17);</SCRIPT>
</NOBR></P>
<A NAME="17061"></A>
<UL><PRE>
bool operator==(const Rational& lhs, // an operator==
const Rational& rhs); // for Rationals
</PRE>
</UL><A NAME="17064"></A>
<UL><PRE>Rational a, b, c, d;
</PRE>
</UL><A NAME="17062"></A>
<UL><PRE>...
</PRE>
</UL><A NAME="17063"></A>
<UL><PRE>if ((a * b) == (c * d)) {
</PRE>
</UL><A NAME="17998"></A>
<UL><PRE> <EM>do whatever's appropriate when the products are equal;</EM>
</PRE>
</UL><A NAME="17999"></A>
<UL><PRE>} else {
</PRE>
</UL><A NAME="18000"></A>
<UL><PRE> <EM>do whatever's appropriate when they're not;</EM>
</PRE>
</UL><A NAME="18001"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="17015"></A>
<P><A NAME="dingp18"></A>
Now ponder this: the expression <CODE>((a*b)</CODE> <CODE>==</CODE> <CODE>(c*d))</CODE> will <EM>always</EM> evaluate to <CODE>true</CODE>, regardless of the values of <CODE>a</CODE>, <CODE>b</CODE>, <CODE>c</CODE>, and <CODE>d</CODE>!<SCRIPT>create_link(18);</SCRIPT>
</P>
<A NAME="17078"></A>
<P><A NAME="dingp19"></A>
It's easiest to understand this vexing behavior by rewriting the test for equality in its equivalent functional <NOBR>form:<SCRIPT>create_link(19);</SCRIPT>
</NOBR></P>
<A NAME="17076"></A>
<UL><PRE>if (operator==(operator*(a, b), operator*(c, d)))
</PRE>
</UL><A NAME="17077"></A>
<P><A NAME="dingp20"></A>
Notice that when <CODE>operator==</CODE> is called, there will already be <EM>two</EM> active calls to <CODE>operator*</CODE>, each of which will return a reference to the static <CODE>Rational</CODE> object inside <CODE>operator*</CODE>. Thus, <CODE>operator==</CODE> will be asked to compare the value of the static <CODE>Rational</CODE> object inside <CODE>operator*</CODE> with the value of the static <CODE>Rational</CODE> object inside <CODE>operator*</CODE>. It would be surprising indeed if they did not compare equal. <NOBR>Always.<SCRIPT>create_link(20);</SCRIPT>
</NOBR></P>
<A NAME="17103"></A>
<P><A NAME="dingp21"></A>
<A NAME="p105"></A>With luck, this is enough to convince you that returning a reference from a function like <CODE>operator*</CODE> is a waste of time, but I'm not so naive as to believe that luck is always sufficient. Some of you — and you know who you are — are at this very moment thinking, "Well, if <i>one</i> static isn't enough, maybe a static <I>array</I> will do the <NOBR>trick..."<SCRIPT>create_link(21);</SCRIPT>
</NOBR></P>
<A NAME="17132"></A>
<P><A NAME="dingp22"></A>
Stop. Please. Haven't we suffered enough <NOBR>already?<SCRIPT>create_link(22);</SCRIPT>
</NOBR></P>
<A NAME="17163"></A>
<P><A NAME="dingp23"></A>
I can't bring myself to dignify this design with example code, but I can sketch why even <EM>entertaining</EM> the notion should cause you to blush in shame. First, you must choose <EM>n</EM>, the size of the array. If <EM>n</EM> is too small, you may run out of places to store function return values, in which case you'll have gained nothing over the single-<CODE>static</CODE> design we just discredited. But if <I>n</I> is too big, you'll decrease the performance of your program, because <I>every</I> object in the array will be constructed the first time the function is called. That will cost you <I>n</I> constructors and <I>n</I> destructors, even if the function in question is called only once. If "optimization" is the process of improving software performance, this kind of thing should be called "pessimization." Finally, think about how you'd put the values you need into the array's objects and what it would cost you to do it. The most direct way to move a value between objects is via assignment, but what is the cost of an assignment? In general, it's about the same as a call to a destructor (to destroy the old value) plus a call to a constructor (to copy over the new value). But your goal is to avoid the costs of construction and destruction! Face it: this approach just isn't going to pan <NOBR>out.<SCRIPT>create_link(23);</SCRIPT>
</NOBR></P>
<A NAME="6247"></A>
<P><A NAME="dingp24"></A>
No, the right way to write a function that must return a new object is to have that function return a new object. For <CODE>Rational</CODE>'s <CODE>operator*</CODE>, that means either the following code (which we first saw back on <A HREF="#6223" TARGET="_top">page 102</A>) or something essentially <NOBR>equivalent:<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<A NAME="17208"></A>
<UL><PRE>inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
</PRE>
</UL></P>
<A NAME="17205"></A>
<P><A NAME="dingp25"></A>
Sure, you may incur the cost of constructing and destructing <CODE>operator*</CODE>'s return value, but in the long run, that's a small price to pay for correct behavior. Besides, the bill that so terrifies you may never arrive. Like all programming languages, C++ allows compiler implementers to apply certain optimizations to improve the performance of the generated code, and it turns out that in some cases, <CODE>operator*</CODE>'s return value can be safely eliminated (see <A HREF="../MEC./MI20_FR.HTM#45310" TARGET="_top">Item M20</A>). When compilers take advantage of that fact (and current compilers often do), your pro<A NAME="p106"></A>gram continues to behave the way it's supposed to, it just does it faster than you <NOBR>expected.<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P>
<A NAME="17289"></A>
<P><A NAME="dingp26"></A>
It all boils down to this: when deciding between returning a reference and returning an object, your job is to make the choice that does the right thing. Let your compiler vendors wrestle with figuring out how to make that choice as inexpensive as <NOBR>possible.<SCRIPT>create_link(26);</SCRIPT>
</NOBR></P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI22_FR.HTM" TARGET="_top">Item 22: Prefer pass-by-reference to pass-by-value.</A> <BR> Continue to <A HREF="./EI24_FR.HTM" TARGET="_top">Item 24: Choose carefully between function overloading and parameter defaulting.</A></FONT></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -