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)!

参考文献: 《More Effective C++ 35个改善编程与设计的有效方法 中文版》

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页