试想你正在阅读另一个程序员写好的函数代码,而这个函数中却有至少三个地方用到了不必要的临时对象,你能发现几个,程序员又该如何修改?
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 " ";
}
【解答】
两个明显的临时对象隐藏在函数声明中:
string FindAddr(list<Employee> l,string name)
1和2两处:两个参数都应该使用常量应用(const reference),使用传值方式将导致函数对list和string拷贝,其性能代码是高昂的。
【规则】:请使用const&而不是传值拷贝。
for(list<Employee>::iterator i = l.begin();
i != l.end(); i++)
第3处:先增(preincrement)比后增(postincrement)操作效率高,因为进行后增操作时,对象不但必须自己递增,而且还要返回一个包含递增之前的值的临时对象,内置类型int这样都如此。
【学习指导】:请使用先增(preincrement)操作,避免使用后增(postincrement)操作。
if(*i == name)
第4处:这里没有体现Employee类,如果想让它行得通,则要么能转换成一个string对象,要么通过一个转换构造函数来得到一个string。然而这两种方法都会产生临时对象,从而导致string或者Employee的operator==调用。
【学习指导】:时刻注意因为参数转换操作而产生的临时对象。一个避免它的好办法就是尽可能显示(explicit)的使用构造函数(constructor)。
return " ";
第5处:这里产生一个临时的(空的)string对象。更好的做法是,声明一个string对象来存储返回值,然后用一个单独的return语句返回这个string。这样使得编译器在某些情况下启用“返回值最优化”处理来省略掉临时对象。
【规则】:请遵循使用所谓的“单入口/单出口”(single-entry/single-exit)规则。绝不要在一个函数里写多个return语句。
【作者记:当进行了进一步性能测试之后,我不再认同上面这条建议。我已经在《Exceptional C++》中修改了这一点。】
string FindAddr(list<Employee> l,string name)
第*点:这可是一处遮眼法(red herring)。看上去,好像你可以很简单的把返回值声明成string&而不是string,来避免不不要的临时对象。这样的做法是错误的,如果你的程序只是在代码试图使用引用的时候崩溃(因为那个所指的局部对象不存在了),那你就够幸运,如果你不走运,你的代码看似能够正常工作,却时不时冷不防失败几次,从而使你不得不在调试程序中度过。
【规则】:绝对绝对不要返回一个局部对象的引用(reference)。
【作者记:有一些帖子正确的指出,你可以声明一个遇到错误才返回的静态对象,从而实现不改变函数语意的情况下返回一个引用。同时意味着,在返回引用的时候,你必须注意对象的生存周期。】
其实还有很多地方可以优化:诸如“避免对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;
}