20.「戻り値最適化(RVO)」の完了に協力する


最後に、関数が値伝達方式(by value)によってオブジェクトを返す場合、プログラムの効率に深刻な影響を及ぼす一時オブジェクトを生成することは避けられないと述べた.以下の例では、2つの式の積を計算する.
 
 
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                 》