マイ整数

数値クラスを新しく定義し、演算子 (e.g., 加算) を定義するときの注意点について。
どうも、一時オブジェクト周りの最適化 (RVOとか) が想定していた通りには効いていない事に気づいたので、メモ。

#include <iostream>

#define PP(x) do{std::cout<<x<<std::endl;}while(0)
#define QQQ(x) do{std::cout<<"@@"<<__LINE__<<"@@ ("<<#x<<") = "    \
    <<(x)<<std::endl;}while(0)
#define LLL do{std::cout<<"----"<<__LINE__<<"----"<<std::endl;}    \
    while(0)

template<typename T>
class MyInt
{
public:

    MyInt ( T value = 0 )
        : value_ ( value ) { PP( " 1:" << this ); }

    MyInt ( const MyInt& rhs )
        : value_ ( rhs.value_ ) { PP( " 2:" << this ); }

    ~MyInt () { PP( " 3:" << this ); }

    T  value () const { return value_; }

    const MyInt& operator+= ( const MyInt& rhs ) {
        value_ += rhs.value_;
        return *this;
    }

    MyInt member_plus ( const MyInt& rhs ) const {
        MyInt  temp = *this;
        return temp += rhs;
    }

public:

    T  value_;
};

template<typename T>
MyInt<T>  operator+ ( MyInt<T> lhs, const MyInt<T>& rhs ) {
    return lhs += rhs;
}

template<typename T>
std::ostream&  operator<< ( std::ostream& os, const MyInt<T>& x ) {
    return os << x.value();
}

int main () {
    MyInt<int>  x ( 999 );
    MyInt<int>  y ( 100 );
    LLL;{ MyInt<int>  z ( x ); QQQ( z += y ); } // ※1
    LLL;{ QQQ( x + y ); } // ※2
    LLL;{ QQQ( x.member_plus(y) ); } // ※3
    LLL;return 0;
}

結果:

 1:0xbfeb6224
 1:0xbfeb6220
----69----
 2:0xbfeb6210
@@69@@ (z += y) = 1099
 3:0xbfeb6210
----70----
 2:0xbfeb621c
 2:0xbfeb6218
@@70@@ (x + y) = 1099
 3:0xbfeb6218
 3:0xbfeb621c
----71----
 2:0xbfeb6210
 2:0xbfeb6214
 3:0xbfeb6210
@@71@@ (x.member_plus(y)) = 1099
 3:0xbfeb6214
----76----
 3:0xbfeb6220
 3:0xbfeb6224

副作用なしの加算をしたいとき、まず一時オブジェクトを作ってそちらに副作用ありの加算を適用するというのが普通のはず。
で、MyInt z = x して z += y したときの結果 (※1) が理想。一時オブジェクトは一個しか作られない。一個作られるのは仕方ない(一個も作りたくない時は += するべき)ので、これが最適。

x+y で自動的に作られることを期待したやり方でうまくいくと思っていたけれど、ダメ(※2)。一時オブジェクトが都合2個つくられてしまう。
ひょっとして、メンバ関数として演算子を定義すればよいのかと思ったけど、それに相当すると考えられる※3でもダメ。


こういうとき、どうするのがいいんでしょうかねー。

追記(+ソースコードの余計な関数を修正。詳細は項目を改めて言及)

どうも、const& で返せばよいらしい事が判明。ジョーシキなんかなぁ?

template<typename T>
class MyInt {
///..snip..
    const MyInt& operator+= ( const MyInt& rhs ) {
        value_ += rhs.value_;
        return *this;
    }

    const MyInt& member_plus ( const MyInt& rhs ) const {
        MyInt  temp (*this);
        return temp += rhs;
    }

    const MyInt& member_plus2 ( MyInt rhs ) const {
        return rhs += *this;
    }
///..snip..
};
///..snip..
template<typename T>
const MyInt<T>&  operator+ ( MyInt<T> lhs, const MyInt<T>& rhs ) {
    return lhs += rhs;
}

template<typename T>
std::ostream&  operator<< ( std::ostream& os, const MyInt<T>& x ) {
    return os << x.value();
}

int main () {
    MyInt<int>  x ( 999 );
    MyInt<int>  y ( 100 );
    LLL;{ MyInt<int>  z ( x ); QQQ( z += y ); }
    LLL;{ QQQ( x + y ); }
    LLL;{ QQQ( x.member_plus(y) ); }
    LLL;{ QQQ( x.member_plus2(y) ); }
    LLL;return 0;
}
 1:0xbfbc4734
 1:0xbfbc4730
----69----
 2:0xbfbc4724
@@69@@ (z += y) = 1099
 3:0xbfbc4724
----70----
 2:0xbfbc472c
@@70@@ (x + y) = 1099
 3:0xbfbc472c
----71----
 2:0xbfbc4724
 3:0xbfbc4724
@@71@@ (x.member_plus(y)) = 1099
----72----
 2:0xbfbc4728
@@72@@ (x.member_plus2(y)) = 1099
 3:0xbfbc4728
----77----
 3:0xbfbc4730
 3:0xbfbc4734

const&周りはよく分かってないのでいずれ調べねば。。