20. 协助完成“返回值优化(RVO)”
[19]最后曾提到了在函数通过传值方式(by value)返回一个对象时,不可避免地要生成一个临时对象,这会严重影响到程序的效率,如下例计算两个分式的乘积:
class CRational{ public: CRational(int numerator, int denominator) { this->numerator = numerator; this->denominator = denominator; } int numer() const //get numerator { return numerator; } int denom() const //get denominator { return denominator; } private: int numerator; int denominator; }; const CRational operator *(const CRational& lhs, const CRational& rhs) { CRational res(lhs.numer() * rhs.numer(), lhs.denom() * rhs.denom()); return res; } CRational a(1, 3); CRational b(2, 3); CRational c = a * b; // 调用函数 operator *()
我们来仔细分析一下operator * 完成的功能,生成一个局部对象res ,调用构造函数进行了初始化,函数返回时,还会生成一个临时变量,用res进行copy constructor,返回之后会销毁res对象,而调用c = a * b; 时将临时对象用来初始化c,然后再销毁这个临时变量。
上面这一系列的构造、析构对象,严重影响了程序的性能,那么有什么办法可以消除这种影响呢?
能否通过返回一个对象指针呢?将实例的函数改为:
const CRational* operator *(const CRational& lhs, const CRational& rhs) { CRational *pres = new CRational(lhs.numer() * rhs.numer(), lhs.denom() * rhs.denom()); return pres; } CRational c = * (a * b); //调用函数
暂不说最后调用函数的表达式看起来很不自然,最严重的问题是这样写容易导致内存泄漏,因为我们往往会忘记释放函数返回来的指针。
那能否通过返回对象的reference呢?于是上面的实例的函数被修改为:
const CRational& operator *(const CRational& lhs, const CRational& rhs) { CRational res(lhs.numer() * rhs.numer(), lhs.denom() * rhs.denom()); return res; } CRational c = a * b; //看起来没啥问题对吧?
貌似这种做法,可以不用生成临时对象,因为返回的是对象的引用,直接指向res。事实上,这种做法是错误的!函数返回的是reference,指向一个局部对象,而局部对象在函数返回时是要被释放的,因此res在operator *返回时已经不存在了,所以这种做法是不被编译器允许的!
额...貌似没什么其他办法消除返回的临时对象了,那么能否通过其他方式让编译器消除生成临时对象的成本呢?
我们的做法是:返回constructor arguments 取代局部对象!如下:
const CRational operator *(const CRational& lhs, const CRational& rhs) { return res(lhs.numer() * rhs.numer(), lhs.denom() * rhs.denom()); }
看起来和最开始的做法没什么区别吧....因为还是要生成函数内部临时对象已经函数返回临时对象!但是,这种做法下C++允许编译器将临时对象进行优化,使它们不存在。如调用
CRational c = a * b;
临时对象构造与c的内存内,这样整个过程,你只需付出一个constructor(用来生成c)的代价,而并没有函数内部和返回时临时对象的构造和析构的代价了。
如果将函数再定义为内联函数,将又会节省函数调用的成本。下面是一个最有效的做法:
inline const CRational operator *(const CRational& lhs, const CRational& rhs) { return res(lhs.numer() * rhs.numer(), lhs.denom() * rhs.denom()); } CRational c = a * b; //与下面的语句几乎具有相同的代价 CRational c(a.numer() * b.numer(), a.denom() * b.denom());
而编译器这种优化行为,被称为"Return Value Optimization"(RVO)!
邮箱:haifenglinying#yahoo.cn (#->@)
个人主页:www.hazirguo.com