[More Effective C++]条款22有关返回值优化的验证结果
2013-08-22 19:24
309 查看
(这里的验证结果是针对返回值优化的,其实和条款22本身所说的,考虑以操作符复合形式(op=)取代其独身形式(op),关系不大。书生注)
在[More Effective C++]条款22的最后,在返回值的返回方式上,大师Meyers推荐使用表达式[returnT(lhs)+=rhs;]这种使用匿名临时变量的方式,理由是“自古以来未具名对象总是比具名对象更容易被消除”,这种写法将更好地帮助编译器实现返回值优化(ReturnValue Optimization,简写RVO)。
针对上述说法,我在两款编译器上验证了一下(g++ 4.1.2,以下简称g++,及MS Visual C++2008,以下简称vc),结果并不如此。验证分三个阶段,具体结果如下。
(1)(不做加法计算,纯为RVO验证)
return T(lhs);
结果:g++和vc在不开编译优化选项的情况下,都进行了优化。即只会发生一次copy ctor。
(2)
return T(lhs) += rhs;
或者
T temp(lhs);
return temp += rhs;
结果:g++和vc即使开编译优化选项,也不会优化。即发生两次copyctor,分别在temp对象的构造时与return时。
(3)
T temp(lhs);
temp += rhs;
return temp;
结果:g++无论开不开编译优化选项,都会优化,也就是说针对具名对象temp做了优化。结果只发生一次copy ctor。
vc在不开编译优化选项的情况下,不会优化,打开优化选项的情况下(比如release模式下)会做优化。
乍看(2)的第一种写法和(3)的写法对比,会产生奇怪的错觉:(3)的具名对象都可以优化,为什么(2)的匿名临时对象反而不会优化?其实看看(2)的第二种写法就明白了,问题不在于具名对象还是匿名对象,问题在于return语句上附带的操作。
为了搞清楚这个问题,我们需要先来了解一下返回值优化的原理。根据C++标准规定,具名对象也可以经由RVO被优化去除,那么编译器如何优化呢?按照(3)的流程,正常情况下(不优化)应该这样做:
T temp(lhs);
//经由copyctor创建一个temp对象
temp += rhs;
//在temp上进行操作
return temp;
//返回temp,即经由copyctor将temp对象复制给外层的一个匿名临时对象(或某具名对象)
当编译器判断出函数的返回值是通过temp返回时,就可以将temp对象优化掉,而把在temp身上做的所有操作直接操做于外层的那个匿名临时变量身上,这样最后return所产生的copy操作就可以省掉了。
而在(2)的第二种写法中,temp就没办法被优化了,因为在return语句中,需要在temp对象上来做一个操作,编译器就只能老老实实的将temp构造出来,再以temp为参数做这个操作(调用相应函数),并将该函数返回结果再做一次copyctor复制出去。(2)的第一种写法原理相同。
这个验证结果带给我们的结论就是,如果你定义了一个专用于存储返回值的临时对象(具名或者匿名),那么在返回的时候不要再做多余的操作。另外,如果存在返回不同具名对象的多个路径,编译器将没办法在编译期确定哪个对象会被作为返回值返回,那么编译器也无法完成返回值优化。还有一件事情需要小心,那就是在某些编译器上(比如vc)根据编译选项的不同程序的行为会不同(除了少调用一次copyctor,还少一次临时变量的析构函数调用)。
当然,在优化这件事上,还需要慎重。为了完成返回值优化而硬写出难于理解的代码,甚至可能是效率更差的代码,就违反了我们的本意了。就像Meyers大师所说的,掌握好80-20原则,以及讲求证据的原则。
在[More Effective C++]条款22的最后,在返回值的返回方式上,大师Meyers推荐使用表达式[returnT(lhs)+=rhs;]这种使用匿名临时变量的方式,理由是“自古以来未具名对象总是比具名对象更容易被消除”,这种写法将更好地帮助编译器实现返回值优化(ReturnValue Optimization,简写RVO)。
针对上述说法,我在两款编译器上验证了一下(g++ 4.1.2,以下简称g++,及MS Visual C++2008,以下简称vc),结果并不如此。验证分三个阶段,具体结果如下。
(1)(不做加法计算,纯为RVO验证)
return T(lhs);
结果:g++和vc在不开编译优化选项的情况下,都进行了优化。即只会发生一次copy ctor。
(2)
return T(lhs) += rhs;
或者
T temp(lhs);
return temp += rhs;
结果:g++和vc即使开编译优化选项,也不会优化。即发生两次copyctor,分别在temp对象的构造时与return时。
(3)
T temp(lhs);
temp += rhs;
return temp;
结果:g++无论开不开编译优化选项,都会优化,也就是说针对具名对象temp做了优化。结果只发生一次copy ctor。
vc在不开编译优化选项的情况下,不会优化,打开优化选项的情况下(比如release模式下)会做优化。
乍看(2)的第一种写法和(3)的写法对比,会产生奇怪的错觉:(3)的具名对象都可以优化,为什么(2)的匿名临时对象反而不会优化?其实看看(2)的第二种写法就明白了,问题不在于具名对象还是匿名对象,问题在于return语句上附带的操作。
为了搞清楚这个问题,我们需要先来了解一下返回值优化的原理。根据C++标准规定,具名对象也可以经由RVO被优化去除,那么编译器如何优化呢?按照(3)的流程,正常情况下(不优化)应该这样做:
T temp(lhs);
//经由copyctor创建一个temp对象
temp += rhs;
//在temp上进行操作
return temp;
//返回temp,即经由copyctor将temp对象复制给外层的一个匿名临时对象(或某具名对象)
当编译器判断出函数的返回值是通过temp返回时,就可以将temp对象优化掉,而把在temp身上做的所有操作直接操做于外层的那个匿名临时变量身上,这样最后return所产生的copy操作就可以省掉了。
而在(2)的第二种写法中,temp就没办法被优化了,因为在return语句中,需要在temp对象上来做一个操作,编译器就只能老老实实的将temp构造出来,再以temp为参数做这个操作(调用相应函数),并将该函数返回结果再做一次copyctor复制出去。(2)的第一种写法原理相同。
这个验证结果带给我们的结论就是,如果你定义了一个专用于存储返回值的临时对象(具名或者匿名),那么在返回的时候不要再做多余的操作。另外,如果存在返回不同具名对象的多个路径,编译器将没办法在编译期确定哪个对象会被作为返回值返回,那么编译器也无法完成返回值优化。还有一件事情需要小心,那就是在某些编译器上(比如vc)根据编译选项的不同程序的行为会不同(除了少调用一次copyctor,还少一次临时变量的析构函数调用)。
当然,在优化这件事上,还需要慎重。为了完成返回值优化而硬写出难于理解的代码,甚至可能是效率更差的代码,就违反了我们的本意了。就像Meyers大师所说的,掌握好80-20原则,以及讲求证据的原则。
相关文章推荐
- [More Effective C++]条款22有关返回值优化的验证结果
- [More Effective C++]条款22有关返回值优化的验证结果
- More Effective C++ 条款20 协助完成"返回值优化(RVO)"
- More Effective C++ 条款22
- More Effective C++ (条款22:考虑以操作符复合形式(op=)取代其独身形式(op))
- [More Effective C++]条款二十:协助完成返回值优化
- More Effective C++ 条款22 考虑以操作符复合形式(op=)取代其独身形式(op)
- More Effective C++ 条款6 区别 increment/decrement 操作符的前置(prefix)和后置(postfix)形式
- More Effective C++ 条款16 谨记80-20法则
- More Effective C++ 条款2 最好使用C++转型操作符
- More Effective C++ 条款8 了解各种不同意义的new和delete
- More Effective C++ 条款33 将非尾端(non-leaf classes)设计为抽象类(abstract classes)
- More Effective C++ 条款12 了解”抛出一个exception"与“传递一个参数”或“调用一个虚函数”之间的差异
- More Effective C++ 条款17
- 《More Effective C++》条款27:如何让类对象只在栈(堆)上分配空间?
- [More Effective C++]条款十九:理解临时对象的来源
- More Effective C++(条款四:非必要不提供 default constructor)
- More Effective C++ 条款7 千万不要重载&&,||和,操作符
- More Effective C++ 条款3 绝对不要以多态(polymorphically)方式处理数组
- 读书笔记《More Effective C++》条款1:仔细区别pointers和references