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.