wwwtc1answer.htm
来自「C++builder学习资料C++builder」· HTM 代码 · 共 203 行
HTM
203 行
<HTML>
<HEAD>
<TITLE>Answer What's wrong with this code? Volume #1</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type>
<META content="Harold Howe" name=Author>
<META content="Microsoft FrontPage 4.0" name=GENERATOR></HEAD>
<BODY>
<CENTER>
<TABLE border=0 cellPadding=0 cellSpacing=0 width=640>
<TBODY>
<TR>
<TD>
<H2>Answer What's wrong with this code? Volume #1 </H2>
<H4>The assignment operator </H4>
<P>The assignment operator for the <TT>Foo</TT> class contains a common
programming mistake. Assignment operators should almost always pass their
arguments by const reference. A lot of programmers leave out the const
part, and just pass by reference. When you forget the const keyword, the
code will usually continue to compile and run correctly. However, the code
example in this issue demonstrates a situation where const cannot be
omitted. Recall that the original assignment operator looked like this:
</P><PRE>Foo<B>&</B> <B>operator</B><B>=</B><B>(</B> Foo <B>&</B>rhs<B>)</B>
<B>{</B>
cout <B><<</B> <FONT color=blue>"assignment"</FONT> <B><<</B> endl<B>;</B>
<B>return</B> <B>*</B><B>this</B><B>;</B>
<B>}</B>
</PRE>
<P>The compilation error occurred where an object was being assigned a
return value from a function. </P><PRE>foo1 <B>=</B> GetAFoo<B>(</B><B>)</B><B>;</B> <FONT color=navy>// generates compiler error</FONT>
</PRE>
<P>So why does this assignment fail to compile, when the assignment to
<TT>foo2</TT> compiles without error? </P><PRE>foo2 <B>=</B> foo1<B>;</B> <FONT color=navy>// compiles OK</FONT>
</PRE>
<P>The difference is that <TT>GetAFoo</TT> returns a <TT>Foo</TT> object
by value. Return objects are temporary objects, and temporary objects are
const objects. You cannot modify a temporary object. <TT>foo1</TT> on the
other hand, is not a temporary object, which means that it is not a const
object. </P>
<P>The assignment operator for <TT>Foo</TT> takes a non-const object by
reference. The compiler will not pass const objects to a function that
take references to non-const objects. Why? Because when you pass a value
by reference, the function has the power to alter the original object. The
compiler must ensure that const objects are not changed. </P>
<P>Because the foo assignment operator did not list the <TT>const</TT>
keyword, the compiler will not try to use it for temporary objects. Since
no other suitable assignment operator is available that takes const
parameters, the compiler issues an error. </P>
<P>The solution to this problem is to change the assignment operator so it
takes a constant reference. </P><PRE><B>class</B> Foo
<B>{</B>
<B>public</B><B>:</B>
<B>...</B>
Foo<B>&</B> <B>operator</B><B>=</B><B>(</B><B>const</B> Foo <B>&</B>rhs<B>)</B>
<B>{</B>
cout <B><<</B> <FONT color=blue>"assignment"</FONT> <B><<</B> endl<B>;</B>
<B>return</B> <B>*</B><B>this</B><B>;</B>
<B>}</B>
<B>}</B><B>;</B>
</PRE>
<P>Once this change is made to the <TT>Foo</TT> class, the code example
compiles and runs without error. Here is the output of the program after
making this correction. Note that the comments were added to explain the
output. </P><PRE>default constructor // construct foo1
default constructor // construct foo2
default constructor // construct foo inside GetAFoo
copy construction // copy foo to temporary return object
destructor // destory local foo in GetAFoo
assignment // assign temp return object to foo1
destructor // destory temp object
assignment // assign foo1 to foo2
destructor // end of main, destroy foo1
destructor // and destroy foo2.
</PRE>
<P>In addition to fixing the compilation error, there are a couple of ways
to streamline the code. The first change involves the <TT>GetAFoo</TT>
function, which originally looked like this: </P><PRE>Foo GetAFoo<B>(</B><B>)</B>
<B>{</B>
Foo foo<B>;</B>
<B>return</B> foo<B>;</B>
<B>}</B>
</PRE>
<P>As it stands, this code will construct two <TT>Foo</TT> objects. The
first is the <TT>foo</TT> variable that you see in the code. The second
object is the return object for the function. The <TT>foo</TT> variable is
constructed with the default constructor for the <TT>Foo</TT> class. The
temporary return object is created with the copy constructor. The debug
statements from the program shows how and when these two objects are
created and destroyed.
<P><PRE>...
default constructor // creates foo variable
copy construction // construct temp return object
destructor // destroy foo because it is out of scope
assignment // call assignment operator and pass temp object
destructor // delete temp object
...
</PRE>
<P>As this output shows, calling the <TT>GetAFoo</TT> function results in
the construction and deletion of two <TT>Foo</TT> objects. Because
<TT>GetAFoo</TT> does not use the local <TT>foo</TT> variable for
anything, we can streamline the function by eliminating the variable. The
new and improved <TT>GetAFoo</TT> function looks like this: </P><PRE>Foo GetAFoo<B>(</B><B>)</B>
<B>{</B>
<B>return</B> Foo<B>(</B><B>)</B><B>;</B>
<B>}</B>
</PRE>
<P>
<TABLE width="75%">
<TBODY>
<TR>
<TD vAlign=top><IMG align=top alt=Note border=0 hspace=0
src="images/exclamation.gif" width="32" height="48"> </TD>
<TD vAlign=top><B>Note:</B>
<HR SIZE=1>
This optimization is called the <I>Return Value Optimization</I>.
Compilers do not have to provide this optimization, but most of them
do. All versions of C++Builder implement this optimization. Scott
Meyers discusses this optimization technique in his book <A
href="http://www.amazon.com/exec/obidos/ISBN=020163371X/cbuilderfoundatiA/"
target=_top>More Effective C++</A>.
<HR SIZE=1>
</TD></TR></TBODY></TABLE>
<P>This code eliminates the temporary variable, the copy constructor that
was needed to initialize it, and the destructor that was needed to
deallocate it. The debug output from the streamlined function looks like
this. </P><PRE>...
default constructor // create return object with default constructor
assignment // pass return object to assignment operator
destructor // destroy return object
...
</PRE>
<P>There is one other place where the code can be improved. Notice how the
<TT>main</TT> function creates and initializes the two <TT>Foo</TT>
objects. </P><PRE><B>int</B> main<B>(</B><B>)</B>
<B>{</B>
Foo foo1<B>;</B> <FONT color=navy>// default constructor</FONT>
Foo foo2<B>;</B> <FONT color=navy>// default constructor</FONT>
foo1 <B>=</B> GetAFoo<B>(</B><B>)</B><B>;</B> <FONT color=navy>// Assignment</FONT>
foo2 <B>=</B> foo1<B>;</B> <FONT color=navy>// Assignment</FONT>
<B>return</B> <FONT color=blue>0</FONT><B>;</B>
<B>}</B>
</PRE>
<P>When <TT>main</TT> is structured like this, four function calls are
needed to initialize two objects. When can reduce this to two calls if by
eliminating the use of the default constructors. </P><PRE><B>int</B> main<B>(</B><B>)</B>
<B>{</B>
Foo foo1<B>(</B>GetAFoo<B>(</B><B>)</B><B>)</B><B>;</B> <FONT color=navy>// copy constructor</FONT>
Foo foo2<B>(</B>foo1<B>)</B><B>;</B> <FONT color=navy>// copy constructor</FONT>
<B>return</B> <FONT color=blue>0</FONT><B>;</B>
<B>}</B>
</PRE>
<P>Ironically, this code enhancement eliminates the use of the assignment
operator, and the assignment operator was the original cause of our
compiler errors. After making these improvements to the code, the final
source should look like this: </P><PRE><FONT color=green>#include <iostream></FONT>
<B>using</B> <B>namespace</B> std<B>;</B>
<B>class</B> Foo
<B>{</B>
<B>public</B><B>:</B>
Foo<B>(</B><B>)</B>
<B>{</B>
cout <B><<</B> <FONT color=blue>"default constructor"</FONT> <B><<</B> endl<B>;</B>
<B>}</B>
Foo<B>(</B><B>const</B> Foo <B>&</B><B>)</B>
<B>{</B>
cout <B><<</B> <FONT color=blue>"copy construction"</FONT> <B><<</B> endl<B>;</B>
<B>}</B>
<B>~</B>Foo<B>(</B><B>)</B>
<B>{</B>
cout <B><<</B> <FONT color=blue>"destructor"</FONT> <B><<</B> endl<B>;</B>
<B>}</B>
Foo<B>&</B> <B>operator</B><B>=</B><B>(</B><B>const</B> Foo <B>&</B>rhs<B>)</B>
<B>{</B>
cout <B><<</B> <FONT color=blue>"assignment"</FONT> <B><<</B> endl<B>;</B>
<B>return</B> <B>*</B><B>this</B><B>;</B>
<B>}</B>
<B>}</B><B>;</B>
Foo GetAFoo<B>(</B><B>)</B>
<B>{</B>
<B>return</B> Foo<B>(</B><B>)</B><B>;</B>
<B>}</B>
<B>int</B> main<B>(</B><B>)</B>
<B>{</B>
Foo foo1<B>(</B>GetAFoo<B>(</B><B>)</B><B>)</B><B>;</B>
Foo foo2<B>(</B>foo1<B>)</B><B>;</B>
<B>return</B> <FONT color=blue>0</FONT><B>;</B>
<B>}</B>
</PRE>
<P>That does it for this edition of "What's wrong with this code?" Unless
of course, you can find more ways to improve the final version of the
code. Since this is the first edition of w3TC, I would be interested in
your feedback. Do you like these sorts of articles, or did this article
suck as much as the code that was in it? Feedback is welcome. If you have
ideas for future articles, I would be happy to hear them. Just click the
email link at the bottom of the page.
</P></TD></TR></TBODY></TABLE></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?