📄 ei22.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>Effective C++, 2E | Item 22: Prefer pass-by-reference to pass-by-value</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 LANGUAGE="Javascript">
var dingbase = "EI22_DIR.HTM";
var dingtext = "Item E22, P";
if (self == top) {
top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- SectionName="E22: Prefer pass-by-reference to pass-by-value." -->
<A NAME="6133"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI21_FR.HTM" TARGET="_top">Item 21: Use const whenever possible.</A> <BR> Continue to <A HREF="./EI23_FR.HTM" TARGET="_top">Item 23: Don't try to return a reference when you must return an object. </A></FONT></DIV>
<P><A NAME="dingp1"></A><A NAME="p98"></A><FONT ID="eititle">Item 22: Prefer pass-by-reference to pass-by-value.</FONT><SCRIPT>create_link(1);</SCRIPT>
</P>
<A NAME="6134"></A>
<P><A NAME="dingp2"></A>
In C, everything is passed by value, and C++ honors this heritage by adopting the pass-by-value convention as its default. Unless you specify otherwise, function parameters are initialized with <I>copies</I> of the actual arguments, and function callers get back a <I>copy</I> of the value returned by the <NOBR>function.<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P>
<A NAME="6136"></A>
<P><A NAME="dingp3"></A>
As I pointed out in the <A HREF="./EIINTRFR.HTM" TARGET="_top">Introduction</a> to this book, the meaning of passing an object by value is defined by the copy constructor of that object's class. This can make pass-by-value an extremely expensive operation. For example, consider the following (rather contrived) class <NOBR>hierarchy:<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P>
<A NAME="6143"></A>
<UL><PRE>class Person {
public:
Person(); // parameters omitted for
// simplicity
~Person();
</PRE>
</UL><A NAME="6145"></A>
<UL><PRE> ...
</PRE>
</UL><A NAME="17957"></A>
<UL><PRE>private:
string name, address;
};
</PRE>
</UL><A NAME="6148"></A>
<UL><PRE>class Student: public Person {
public:
Student(); // parameters omitted for
// simplicity
~Student();
</PRE>
</UL><A NAME="6150"></A>
<UL><PRE> ...
</PRE>
</UL><A NAME="17963"></A>
<UL><PRE>private:
string schoolName, schoolAddress;
};
</PRE>
</UL><A NAME="6152"></A>
<P><A NAME="dingp4"></A>
Now consider a simple function <CODE>returnStudent</CODE> that takes a <CODE>Student</CODE> argument (by value) and immediately returns it (also by value), plus a call to that <NOBR>function:<SCRIPT>create_link(4);</SCRIPT>
</NOBR></P>
<A NAME="6154"></A>
<UL><PRE>Student returnStudent(Student s) { return s; }
</PRE>
</UL><A NAME="6155"></A>
<UL><PRE>
Student plato; // Plato studied under
// Socrates
</PRE>
</UL><A NAME="6156"></A>
<UL><PRE>
returnStudent(plato); // call returnStudent
</PRE>
</UL><A NAME="6157"></A>
<P><A NAME="dingp5"></A>
What happens during the course of this innocuous-looking function <NOBR>call?<SCRIPT>create_link(5);</SCRIPT>
</NOBR></P>
<A NAME="6158"></A>
<P><A NAME="dingp6"></A>
The simple explanation is this: the <CODE>Student</CODE> copy constructor is called to initialize <CODE>s</CODE> with <CODE>plato</CODE>. Then the <CODE>Student</CODE> copy constructor is called <A NAME="p99"></A>again to initialize the object returned by the function with <CODE>s</CODE>. Next, the destructor is called for <CODE>s</CODE>. Finally, the destructor is called for the object returned by <CODE>returnStudent</CODE>. So the cost of this do-nothing function is two calls to the <CODE>Student</CODE> copy constructor and two calls to the <CODE>Student</CODE> <NOBR>destructor.<SCRIPT>create_link(6);</SCRIPT>
</NOBR></P>
<A NAME="6160"></A>
<P><A NAME="dingp7"></A>
But wait, there's more! A <CODE>Student</CODE> object has two <CODE>string</CODE> objects within it, so every time you construct a <CODE>Student</CODE> object you must also construct two <CODE>string</CODE> objects. A <CODE>Student</CODE> object also inherits from a <CODE>Person</CODE> object, so every time you construct a <CODE>Student</CODE> object you must also construct a <CODE>Person</CODE> object. A <CODE>Person</CODE> object has two additional <CODE>string</CODE> objects inside it, so each <CODE>Person</CODE> construction also entails two more <CODE>string</CODE> constructions. The end result is that passing a <CODE>Student</CODE> object by value leads to one call to the <CODE>Student</CODE> copy constructor, one call to the <CODE>Person</CODE> copy constructor, and four calls to the <CODE>string</CODE> copy constructor. When the copy of the <CODE>Student</CODE> object is destroyed, each constructor call is matched by a destructor call, so the overall cost of passing a <CODE>Student</CODE> by value is six constructors and six destructors. Because the function <CODE>returnStudent</CODE> uses pass-by-value twice (once for the parameter, once for the return value), the complete cost of a call to that function is <I>twelve</I> constructors and <I>twelve</I> <NOBR>destructors!<SCRIPT>create_link(7);</SCRIPT>
</NOBR></P>
<A NAME="6161"></A>
<P><A NAME="dingp8"></A>
In fairness to the C++ compiler-writers of the world, this is a worst-case scenario. Compilers are allowed to eliminate some of these calls to copy constructors. (The <NOBR><FONT COLOR="#FF0000" SIZE="-2"><B>°</B></FONT><A HREF="http://www.awl.com/cseng/cgi-bin/cdquery.pl?name=cstandard" onMouseOver = "self.status = 'The latest publicly-available version of the C++ standard'; return true" onMouseOut = "self.status = self.defaultStatus" TARGET="_top">C++</NOBR> standard</A> — see <A HREF="./EI50_FR.HTM#8569" TARGET="_top">Item 50</A> — describes the precise conditions under which they are allowed to perform this kind of magic, and <A HREF="../MEC./MI20_FR.HTM#45310" TARGET="_top">Item M20</A> gives examples). Some compilers take advantage of this license to optimize. Until such optimizations become ubiquitous, however, you've got to be wary of the cost of passing objects by <NOBR>value.<SCRIPT>create_link(8);</SCRIPT>
</NOBR></P>
<A NAME="6168"></A>
<P><A NAME="dingp9"></A>
To avoid this potentially exorbitant cost, you need to pass things not by value, but by <NOBR>reference:<SCRIPT>create_link(9);</SCRIPT>
</NOBR></P>
<A NAME="6170"></A>
<UL><PRE>const Student& returnStudent(const Student& s)
{ return s; }
</PRE>
</UL><A NAME="6172"></A>
<P><A NAME="dingp10"></A>
This is much more efficient: no constructors or destructors are called, because no new objects are being <NOBR>created.<SCRIPT>create_link(10);</SCRIPT>
</NOBR></P>
<A NAME="6173"></A>
<P><A NAME="dingp11"></A>
Passing parameters by reference has another advantage: it avoids what is sometimes called the "slicing problem." When a derived class object is passed as a base class object, all the specialized features that make it behave like a derived class object are "sliced" off, and you're left with a simple base class object. This is almost never what you want. For example, suppose you're working on a set of classes for implementing a graphical window <NOBR>system:<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P>
<A NAME="6176"></A>
<UL><PRE><A NAME="p100"></A>class Window {
public:
string name() const; // return name of window
virtual void display() const; // draw window and contents
};
</PRE>
</UL><A NAME="6178"></A>
<UL><PRE>class WindowWithScrollBars: public Window {
public:
virtual void display() const;
};
</PRE>
</UL><A NAME="6179"></A>
<P><A NAME="dingp12"></A>
All <CODE>Window</CODE> objects have a name, which you can get at through the <CODE>name</CODE> function, and all windows can be displayed, which you can bring about by invoking the <CODE>display</CODE> function. The fact that <CODE>display</CODE> is virtual tells you that the way in which simple base class <CODE>Window</CODE> objects are displayed is apt to differ from the way in which the fancy, high-priced <CODE>WindowWithScrollBars</CODE> objects are displayed (see Items <A HREF="./EI36_FR.HTM#7007" TARGET="_top">36</A>, <A HREF="./EI37_FR.HTM#7167" TARGET="_top">37</A>, and <A HREF="../MEC/MI33_FR.HTM#10947" TARGET="_top">M33</A>).<SCRIPT>create_link(12);</SCRIPT>
</P>
<A NAME="6186"></A>
<P><A NAME="dingp13"></A>
Now suppose you'd like to write a function to print out a window's name and then display the window. Here's the <I>wrong</I> way to write such a <NOBR>function:<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<A NAME="6187"></A>
<UL><PRE>// a function that suffers from the slicing problem
void printNameAndDisplay(Window w)
{
cout << w.name();
w.display();
}
</PRE>
</UL><A NAME="6189"></A>
<P><A NAME="dingp14"></A>
Consider what happens when you call this function with a <CODE>WindowWithScrollBars</CODE> <NOBR>object:<SCRIPT>create_link(14);</SCRIPT>
</NOBR></P>
<A NAME="6190"></A>
<UL><PRE>WindowWithScrollBars wwsb;
</PRE>
</UL><A NAME="6191"></A>
<UL><PRE>printNameAndDisplay(wwsb);
</PRE>
</UL><A NAME="6192"></A>
<P><A NAME="dingp15"></A>
The parameter <CODE>w</CODE> will be constructed — it's passed by value, remember? — as a <CODE><I>Window</I></CODE> object, and all the specialized information that made <CODE>wwsb</CODE> act like a <CODE>WindowWithScrollBars</CODE> object will be sliced off. Inside <CODE>printNameAndDisplay</CODE>, <CODE>w</CODE> will always act like an object of class <CODE>Window</CODE> (because it <i>is</i> an object of class <CODE>Window</CODE>), regardless of the type of object that is passed to the function. In particular, the call to <CODE>display</CODE> inside <CODE>printNameAndDisplay</CODE> will <I>always</I> call <CODE>Window::display</CODE>, never <CODE>WindowWithScrollBars::display</CODE>.<SCRIPT>create_link(15);</SCRIPT>
</P>
<A NAME="6193"></A>
<P><A NAME="dingp16"></A>
The way around the slicing problem is to pass <CODE>w</CODE> by <NOBR>reference:<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<A NAME="6194"></A>
<UL><PRE><A NAME="p101"></A>// a function that doesn't suffer from the slicing problem
void printNameAndDisplay(const Window& w)
{
cout << w.name();
w.display();
}
</PRE>
</UL><A NAME="6196"></A>
<P><A NAME="dingp17"></A>
Now <CODE>w</CODE> will act like whatever kind of window is actually passed in. To emphasize that <CODE>w</CODE> isn't modified by this function even though it's passed by reference, you've followed the advice of <A HREF="./EI21_FR.HTM#6003" TARGET="_top">Item 21</A> and carefully declared it to be <CODE>const</CODE>; how good of <NOBR>you.<SCRIPT>create_link(17);</SCRIPT>
</NOBR></P>
<A NAME="6198"></A>
<P><A NAME="dingp18"></A>
Passing by reference is a wonderful thing, but it leads to certain complications of its own, the most notorious of which is aliasing, a topic that is discussed in <A HREF="./EI17_FR.HTM#2264" TARGET="_top">Item 17</A>. In addition, it's important to recognize that you sometimes <I>can't</I> pass things by reference; see <A HREF="./EI23_FR.HTM#6210" TARGET="_top">Item 23</A>. Finally, the brutal fact of the matter is that references are almost always <I>implemented</I> as pointers, so passing something by reference usually means really passing a pointer. As a result, if you have a small object — an <CODE>int</CODE>, for example — it may actually be more efficient to pass it by value than to pass it by <NOBR>reference.<SCRIPT>create_link(18);</SCRIPT>
</NOBR></P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI21_FR.HTM" TARGET="_top">Item 21: Use const whenever possible.</A> <BR> Continue to <A HREF="./EI23_FR.HTM" TARGET="_top">Item 23: Don't try to return a reference when you must return an object. </A></FONT></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -