说明
临时对象是隐晦的,但对性能的影响是不可忽视的。
本文通过剖析示例,找出临时对象的藏身之所,曝光并消灭它。
示例
代码片断如下:
string FindAddr(list<Employee> l, string name)
{
for (auto i = l.begin(); i != l.end(); i++)
{
if (*i == name)
{
return (*i).addr;
}
}
return "";
}
这段代码简单到不需要任何的注释,且可能存在于任何规模的项目中。但,这里面有多少处临时对象?
剖析
先说下答案吧,至少有 5处:
- 函数参数
list<Employee> l, string name
:使用传值的方式,性能代价高昂。应该使用引用。 - 循环体中的i++,后置操作符时,对象不但自己递增,还要返回一个包含递增前值的临时对象。内建int i++也是如此。应该使用前置操作符。
- 条件判断语句:
*i == name
,隐含一个隐式转换,从Employee到string,要么通过构造函数,要么操作符,这两种方式都会产生临时对象,可以使用显式声明(explicit)避免。 - 返回语句:
return "";
,会产生一个临时的string对象。可以先定义一个局部string来储存返回值,再使用return返回,利用返回值优化。 - 函数返回类型:string。这是个陷阱,如果把返回类型修改为引用(string&),那么再返回局部变量就会导致对局部对象的引用,造成未定义的行为。
好了,看到了这些问题,也知道了正确的做法,修改版本如下:
string FindAddr(list<Employee>& l, string& name)
{
string addr;
for (auto i = l.begin(); i != l.end(); ++i)
{
if ((*i).name == name)
{
addr = (*i).addr;
break;
}
}
return addr; // 利用了RVO(返回值优化,函数的返回类型与局部变量的类型完全一致时会启用)
}
程序没问题了,但对于标准容器使用遍历式的循环,还是有点不顺眼。
使用标准库
读过《Effective STL》的小伙伴们,是不是也有种对手写循环的反感?那就对了,因为可以使用算法!
使用标准库的算法的好处就不多说了,直接上代码:
string FindAddr(list<Employee>& l, string& name)
{
string addr;
auto i = find(l.begin(), l.end(), name);
if (i != l.end())
{
addr = (*i).addr;
}
return addr; // 利用了RVO(返回值优化,函数的返回类型与局部变量的类型完全一致时会启用)
}
一句话:利用标准库的算法,不要重复造轮子。这样更好、更快、更强!
小结
简单的代码中隐藏着这么多的临时对象,它们看不到,但我们必须要知道。
这对于性能提升来说是必要的,因为只要简单地改动就能达到。
注重效率,从写代码开始。
参考资料
《Exceptional C++》