Temporary Objects
GOTW:#02 临时对象
Difficulty: 5 / 10
[难度系数]:5 / 10
Unnecessary temporaries are frequent
culprits that can throw all your hard work - and
your program 's performance - right out the window.
把你的心血之作(包括你的程序之性能在内)当成垃圾抛出窗外的罪人,往往是一些意想不到的临时对象。
Problem
[问题]
You are doing a code review. A programmer has
written the following function, which uses unnecessary
temporary objects in at least three places.
试想你正在阅读另一个程序员写好的函数代码(如下),而这个函数中却在至少三个地方用到了不必要的临时对象。
How
many can you identify, and how should the programmer
fix them?
那么,你能发现其中几个呢?程序员又该如何修改代码呢?
string FindAddr( list <Employee> l, string name )
{
for( list <Employee> ::iterator i =
l.begin();
i != l.end();
i++ )
{
if( *i == name )
{
return (*i).addr;
}
}
return " ";
}
Solution
[解答]
Believe it or not, these few lines have three obvious
cases of unnecessary temporaries, two subtler ones, and
a red herring.
信不信由你,这短短的几行代码中,起码有三个地方明显使用了不必要的临时对象,其中有两个比较微妙,第三个则是一计遮眼法(red
herring)。
string FindAddr( list <Employee>
l, string name )
^^^^^^^1^^^^^^^^ ^^^^^2^^^^^
1 & 2. The
parameters should be const references. Pass-by-value
copies both the list and the string, which can be
expensive.
1和2:两个参数都应该使用常量引用(const
reference)。使用传值(pass-by-value)方式将会导致函数对list和string进行拷贝,其性能代价是高昂的。
[Rule] Prefer passing const& instead of copied
values.
[规则]:请使用const&而不是传值拷贝。
for( list <Employee> ::iterator i = l.begin();
i != l.end();
i++ )
^3^
3. This one was
more subtle. Preincrement is more efficient than
postincrement, because for postincrement the object must
increment itself and then return a temporary containing
its old value. Note that this is true even for
builtins like int!
3:这一处真是更为微妙。前置递增操作(++i)比后置递增操作(i++)更有效率,这是因为在进行后置递增操作时,对象不但必须自己递增,而且还要返回
一个包含递增前之值的临时对象。要知道,就连int这样的内建类型也是如此!
[Guideline] Prefer
preincrement, avoid postincrement.
[指导方针]:请使用前置递增操作(++i),避免使用后置递增操作(i++)。
if(
*i == name )
^4
4. The Employee class isn 't shown, but for this to
work it must either have a conversion to string or a
conversion ctor taking a string. Both cases create a
temporary object, invoking either operator= for strings
or for Employees. (The only way there wouldn 't be a
temporary is if there was an operator= taking one of
each.)
4:这里没有体现Employee类,但如果想让它行得通,则要么来一个转换成string的操作,要么通过一个转换构造函数(constructor)
来得到一个string。然而两种方法都会产生临时对象,从而导致对string或者Employee的operator=之调用。()
[Guideline] Watch out for hidden temporaries created by
parameter conversions. One good way to avoid this is
to make ctors explicit when possible.
[指导方针]:时刻注意因为参数转换操作而产生隐藏的临时对象。一个避免它的好办法就是尽可能显式(explicit)的使用构造函数
(constructor)。
return " ";
^5
5. This creates a temporary (empty)
string object.
5:这里产生了一个临时的(空的)string对象。
More
subtly, it 's better to declare a local string object
to hold the return value and have a single return
statement that returns that string. This lets the
compiler use the return value optimisation to omit the
local object in some cases, e.g., when the client
code writes something like:
string a = FindAddr( l, "Harold " );
更好的做法是,声明一个局部string对象来储存返回值,然后用单独一个return语句返回这个string。这使得编译器可以在某些情况下(比如,
像string a = FindAddr(l, "Harold ")的形式就启用“返回值优化”处理来省略掉局部对象。
[Rule] Follow the single-entry/single-exit rule. Never
write multiple return statements in the same function.
[规则]:请遵循所谓的“单入口/单出口”(single-entry/single-exit)规则。绝不要在一个函数里面写有多个return语句。
[Note: After more performance testing, I no
longer agree with the above advice. It has been
revised in Exceptional C++.]
[注解:当进行进一步性能测试之后,我不再认同上面的那条建议。我已经在《Exceptional C++》中修改了这一点。]
string FindAddr( list <Employee> l, string
name )
^^^*^^
*. This was a red herring.
It may seem like you could avoid a temporary in all
return cases simply by declaring the return type to
be string& instead of string. Wrong (generally see
note below)! If you 're lucky, your program will crash
as soon as the calling code tries to use the
reference, since the (local!) object it refers to no
longer exists. If you 're unlucky, your code will appear
to work and fail intermittently, causing long nights
of toiling away in the debugger.
* :这可是一计遮眼法(red
herring)。看上去,你可以很简单的通过把返回类型声明为string&而不是string来避免所有临时对象的问题,对吗?错了!如果在
你的程序中试图使用引用的变量时,程序崩溃(因为那个所指向的局部对象已经不存在了),如果你走运的话,你的代码看上去似乎能够正常工作,却有时去会失
败,从而使你不得不在调试程序的过程中渡过一个又一个漫漫长夜。
[Rule] Never, ever, EVER
return references to local objects.
[规则]:绝对绝对(!)不要返回对局部对象的引用(reference)。
(Note: Some posters
correctly pointed out that you could make this a
reference return without changing the function 's semantics
by declaring a static object that is returned on
failure. This illustrates that you do have to be aware
of object lifetimes when returning references.)
[注解:有一些贴子正确的指出,你可以声明一个遇到错误时才返回的静态对象,从而实现在不改变函数语义的情况下返回一个引用(reference)。同时
这也意味着,在返回引用(reference)的时候,你必须注意对象的生命期。]
There are other
optimisation opportunities, such as avoiding the redundant
calls to end(). The programmer could/should also have
used a const_iterator. Ignoring these for now, a
corrected version follows:
其实还有很多可以优化的地方,诸如“避免对end()进行多余的调用”等等。程序员可以(也应该)使用一个const_iterator。抛开这些不谈,
我们写出如下正确代码:
string FindAddr( const list
<Employee> & l, const string& name )
{
string addr;
for( list
<Employee> ::const_iterator i = l.begin();
i != l.end();
++i )
{
if( (*i).name == name
)
{
addr =
(*i).addr;
break;
}
}
return addr;
}
[注释]
red herring: Something that draws attention away
from the central issue.
a red herring: 遮眼法; 转移注意力的东西]
from msdn
Temporary Objects
In some cases, it is
necessary for the compiler to create temporary objects.
These temporary objects can be created for the
following reasons:
To initialize a const
reference with an initializer of a type different from
that of the underlying type of the reference being
initialized.
To store the return value of a
function that returns a user-defined type. These
temporaries are created only if your program does not
copy the return value to an object. For example:
UDT Func1(); // Declare a function that
returns a user-defined
// type.
...
Func1();
// Call Func1, but discard return value.
// A temporary
object is created to store the return
// value.
Because
the return value is not copied to another object, a
temporary object is created. A more common case where
temporaries are created is during the evaluation of an
expression where overloaded operator functions must be
called. These overloaded operator functions return a
user-defined type that often is not copied to another
object.
Consider the expression ComplexResult =
Complex1 + Complex2 + Complex3. The expression Complex1 +
Complex2 is evaluated, and the result is stored in a
temporary object. Next, the expression temporary +
Complex3 is evaluated, and the result is copied to
ComplexResult (assuming the assignment operator is not
overloaded).
To store the result of a cast to
a user-defined type. When an object of a given type
is explicitly converted to a user-defined type, that
new object is constructed as a temporary object.
Temporary objects have a lifetime that is defined by
their point of creation and the point at which they
are destroyed. Any expression that creates more than one
temporary object eventually destroys them in the
reverse order in which they were created. The points
at which destruction occurs are shown in Table 11.3.
Table 11.3 Destruction Points for Temporary
Objects
Reason Temporary Created Destruction Point
Result of expression
evaluation All temporaries
created as a result of expression evaluation are
destroyed at the end of the expression statement (that
is, at the semicolon), or at the end of the
controlling expressions for for, if, while, do, and
switch statements.
Result of expressions using
the
built-in (not overloaded)
logical operators (|| and
&&) Immediately after the right operand. At this
destruction point, all temporary objects created by
evaluation of the right operand are destroyed.
Initializing const references If an initializer is not
an l-value of the same type as the reference being
initialized, a temporary of the underlying object type
is created and initialized with the initialization
expression. This temporary object is destroyed immediately
after the reference object to which it is bound is
destroyed.