Temporary Objects

来源:互联网 发布:王者荣耀段位网络查询 编辑:程序博客网 时间:2024/06/10 22:59

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.  

原创粉丝点击