原文在
http://www.gotw.ca/gotw/002.htm自己翻着学的。看过Exceptional C++的可以跳过。想批评啥说啥,随您
临时对象:
难度:5/10
(比#1高了一点了)
多余的临时对象是个坏蛋惯犯。它总能把你的活计从窗户扔出去,或者让你的代码效率低下。
问题:
你在做code review,(可能你是菜鸟,还没机会review别人的。那就当成是在review自己的代码好了)码农写了个函数,里边至少用了三次非必要的临时对象。(凶手这下是3个以上了)
你能找出几个?该怎么改正?
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 "";
}
(我找了下,找到了3个,有个地方是bug,非改不可。不知道对不对。。 看看答案)
答案:
你信不信,就这么几行就有茫茫多的问题。。。(不愧为码农。。)3次明显的多余临时对象,2个较隐蔽的,1个胡来的。
string FindAddr( list<Employee> l, string name )
^^^^1^^^^ ^^^2^^^
1和2都应该是const引用。传值会导致对两个对象的复制,内存不是拿来这么浪费的呀,当然还有cpu
[规定]多用const引用,少用值传递。
for( list<Employee>::iterator i = l.begin();
i != l.end();
i++ )
^3^
3.这个比较隐蔽。前缀++比后缀++的效率高些。为什么呢?想想他们两个的返回值吧。后缀++会自己增加1,然后把一个带有旧值的临时变量给返回去。这一点对int之类的内部类型同样适用。
[指导意见]:用前缀,这里别用后缀。
if( *i == name )
^4
4.尽管Employee类没列出来,这里有两种可能,把Employee转换成string,或是调用Employee::Employee(string &)做转换。不管是哪种,都会创建一个临时对象,然后调用operator==(),可能是对string的,也可能是Employee。(除非重载operator==(),或者Employee能被转成string引用,而这也是非必要的临时对象)
[指导意见]:注意类型转换时很隐蔽的临时对象。可以的话,把构造函数声明为explicit就能避免。
return "";
^5
5.这里会生成一个临时对象。空string对象。
如果用局部string变量做返回值,并且专门返回它会更好。这样编译器能做一点优化。比如: string a = FindAddr( l, "Harold" );
[规定]单入口单出口。不要写多个返回语句。
注意:经过Herb Shutter(也就是原文作者)测试,这个方法已经被否了。。。(我觉得这个点子本来就非常雷人,他这种大仙怎么会有这种想法的)
在Exceptional C++里,他写了一大堆测试的结果后,说
[规定]小心对象的生存期。永远永远不要返回局部自动对象的指针或引用。因为完全用不上。当然更可怕的是万一被用到了,就更傻眼了。。。
他甚至写了用static局部变量,然后返回引用。可想而知这个点子也不怎么样。不过好在,他又发现一个问题,就是
i != l.end(); ^^^6^^^
6.这是在for循环中的判断条件,很显然,在每一次循环的判断中,l.end()都会生成一个临时对象,然后返回。而这个对象每次都一样,这是一个严重的浪费。改正的办法很简单。在for循环前,把l.end()赋给一个局部对象就可以了,只需要生成一次。
[指导意见]:把恒定的值预先算出来。
最后给一个《Exceptional C++》中改好的
string FindAddr( list<Employee> & l, string & name )
{
list<Employee>::const_iterator end(l.end()); //const_iterator和#1里说了的初始化SomeType t(u)
for( list<Employee>::const_iterator i = l.begin();
i != end;
++i ) //前缀++
{
if( i->name == name ) //这里和下面的i->addr都算是他的想象了
{
return i->addr; //同上
}
}
return "";
}
这一篇参考了《Exceptional C++》原文。