条款6:GotW#2 临时对象(Temporary Objects)

试想你正在阅读另一个程序员写好的函数代码,而这个函数中却有至少三个地方用到了不必要的临时对象,你能发现几个,程序员又该如何修改?

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;
}

猜你喜欢

转载自blog.csdn.net/weixin_28712713/article/details/85316474