クラスの operator を定義するとき、戻り値の型はどうすべきか

クラスの operator を定義するとき、戻り値の型はどうすべきかのまとめ。

サンプルクラス

以下のクラスに、 operator を定義するとします。

全体まとめ

代入演算(「=」)

結論

CPoint3d& CPoint3d::operator = ( const CPoint3d& b );

考察(他の選択肢)

①戻り値の型を void にする。
void CPoint3d::operator = ( const CPoint3d& b );
戻り値の型を void にすると、代入の連鎖ができなくなる。すなわち、
CPoint3d point1(1,1,1), point2(2,2,2), point3(3,3,3);
point1 = point2 = point3;
がコンパイルエラーになる。
組み込み型ではできるので、ユーザー定義型でもできる方がよりよい。
int i1=1, i2=2, i3=3;
i1 = i2 = i3;
はコンパイルエラーにならない。
②戻り値の型に const をつける。
const CPoint3d& CPoint3d::operator = ( const CPoint3d& b );
戻り値の型に const をつけると、
CPoint3d point1(1,1,1), point2(2,2,2), point3(3,3,3);
(point1 = point2) = point3;
がコンパイルエラーになる。
組み込み型ではできるので、ユーザー定義型でもできる方がよりよい。
int i1=1, i2=2, i3=3;
(i1 = i2) = i3;
はコンパイルエラーにならない。

和差積商(「+」「-」「*」「/」)

結論

const CPoint3d CPoint3d::operator + ( const CPoint3d& b ) const;
const CPoint3d CPoint3d::operator - ( const CPoint3d& b ) const;
const CPoint3d CPoint3d::operator * ( const CPoint3d& b ) const;
const CPoint3d CPoint3d::operator / ( const CPoint3d& b ) const;

考察(他の選択肢)

①戻り値の型を void にする。
void CPoint3d::operator + ( const CPoint3d& b ) const;
関数が仕事をしなくなる。
②戻り値の型に const をつけない。
CPoint3d CPoint3d::operator + ( const CPoint3d& b ) const;
戻り値の型に const をつけないと、
CPoint3d point1(1,1,1), point2(2,2,2), point3(3,3,3);
(point1 + point2) = point3;
がコンパイルエラーにならなくなる。
組み込み型ではできないので、ユーザー定義型でもできない方がよりよい。
int i1=1, i2=2, i3=3;
(i1 + i2) = i3;
はコンパイルエラーになる。
③戻り値の型を参照にする。
const CPoint3d& CPoint3d::operator / ( const CPoint3d& b ) const;
関数から返るオブジェクトは、関数内で宣言された一時オブジェクトなので、参照を返してはいけない。

自身に対する和差積商(「+=」「-=」「*=」「/=」)

結論

CPoint3d& CPoint3d::operator += ( const CPoint3d& b );
CPoint3d& CPoint3d::operator -= ( const CPoint3d& b );
CPoint3d& CPoint3d::operator *= ( const CPoint3d& b );
CPoint3d& CPoint3d::operator /= ( const CPoint3d& b );

考察(他の選択肢)

①戻り値の型を void にする。
void CPoint3d::operator += ( const CPoint3d& b );
戻り値の型を void にすると、演算の連鎖ができなくなる。すなわち、
CPoint3d point1(1,1,1), point2(2,2,2), point3(3,3,3);
point1 += point2 += point3;
がコンパイルエラーになる。
組み込み型ではできるので、ユーザー定義型でもできる方がよりよい。
int i1=1, i2=2, i3=3;
i1 += i2 += i3;
はコンパイルエラーにならない。
②戻り値の型に const をつける。
const CPoint3d& operator += ( const CPoint3d& b );
戻り値の型に const をつけると、
CPoint3d point1(1,1,1), point2(2,2,2), point3(3,3,3);
(point1 += point2) = point3;
がコンパイルエラーになる。
組み込み型ではできるので、ユーザー定義型でもできる方がよりよい。
int i1=1, i2=2, i3=3;
(i1 += i2) = i3;
はコンパイルエラーにならない。

比較演算(「==」「!=」「<」「>」)

結論

bool CPoint3d::operator == ( const CPoint3d& b ) const;
bool CPoint3d::operator != ( const CPoint3d& b ) const;
bool CPoint3d::operator < ( const CPoint3d& b ) const;
bool CPoint3d::operator > ( const CPoint3d& b ) const;

考察(他の選択肢)

①戻り値の型を void にする。
void CPoint3d::operator == ( const CPoint3d& b ) const;
関数が仕事をしなくなる。
②戻り値の型に const をつける。
const bool CPoint3d::operator == ( const CPoint3d& b ) const;
constをつけても、つけなくても違いがない。

配列アクセス(「[ ]」)

結論

const double& CPoint3d::operator [ ] ( const long i ) const; // 読み取り用
double& CPoint3d::operator [ ] ( const long i ); // 書き込み用

考察

書き込み用関数は、戻り値の型を非const参照、関数を非constとし、constオブジェクトからは呼び出せないようにする。
書き込み用の関数だけ用意し、読み取り用の関数を用意しないと、非constオブジェクトは値を読み取れるが、constオブジェクトは値を読み取りることができないという問題が生じるので、書き込み用の関数を用意するときには、constオブジェクト用に戻り値の型をconst参照、関数をconstとした読み取り用関数も用意する。

実装例

参考

Effective C++ 改訂2版:15項 operator=を書くときは、*thisへのリファレンスを返そう
Effective C++ 改訂2版:21項 使えるときは、必ずconstを使おう