More Effective C+----(22)単独形式(op)の代わりに演算子の付与形式(op=)を考慮する


Item M 22:演算子の付与形式(op=)を単独形式(op)に置き換えることを考慮する
転載は出典を明記してください
ほとんどのプログラマーは、コードをこのように書くことができると考えています.
x = x + y; x = x - y;

彼らもそう書くことができます
x += y; x -= y;

xおよびyがユーザ定義タイプ(user-defined type)である場合、このようなことは保証されません.C++の場合、operator+、operator=とoperator+=の間には何の関係もありません.そのため、この3つのoperatorを同時に存在させ、所望の関係を持つには、自分で実現しなければなりません.同様に、operator-、*、/、なども同様です.
operatorの割り当て形式(assignment version)(operator+=)とoperatorの個別形式(stand-alone)(operator+)の間に正常な関係があることを確認します.
1つの好ましい方法は、後者(operator+訳者注を指す)が前者(operator+=訳者注を指す)に従って実現されることである(条項M 6参照).これは簡単です.
class Rational {
public:
  ...
  Rational& operator+=(const Rational& rhs);
  Rational& operator-=(const Rational& rhs);
};
 
// operator+   operator+=  ; 
//         const   ,
//  Effective C++  21   109          
const Rational operator+(const Rational& lhs,
                         const Rational& rhs)
{
  return Rational(lhs) += rhs;
} 
// operator-    operator -=    
const Rational operator-(const Rational& lhs,
                         const Rational& rhs)
{
  return Rational(lhs) -= rhs;
}

この例では、operator+=と-=がゼロから実現され、operator+とoperator-は前述の関数を呼び出すことで独自の機能を提供する.
この設計手法を用いてoperatorの付与形式を維持するだけでよい.また、operator付与形式がクラスのpublicインタフェースにあると仮定すると、operatorの単独形式をクラスの友元にする必要はない(Effective C++条項19参照).
すべてのoperatorの個別の形式を全ローカルドメインに配置することを気にしない場合は、個別の形式の関数の作成の代わりにテンプレートを使用できます.
template<class T>
const T operator+(const T& lhs, const T& rhs)
{
  return T(lhs) += rhs;                     //        
} 
template<class T>
const T operator-(const T& lhs, const T& rhs)
{
  return T(lhs) -= rhs;                      //        
} 
...

これらのテンプレートを使用すると、operator付与形式に何らかのタイプを定義するだけで、必要に応じて対応するoperator個別形式が自動的に生成されます.
このように書くのは確かにいいですが、これまで効率の問題を考えていませんでした.効率は本章のテーマです.ここで指摘すべきは3つの効率面の問題である.
第一に、operatorの付与形式は、単一の形式よりも効率的である.なぜなら、単一の形式は新しいオブジェクトを返すため、一時的なオブジェクトの構造および解放にいくつかのオーバーヘッドがあるからである(条項M 19および条項M 20、およびEffective C++条項23を参照).
Operatorの付与形式は結果を左のパラメータに書き込むため,operatorの戻り値を収容するために一時オブジェクトを生成する必要はない.
第二に、operatorの付与形式を提供すると同時に、その標準形式を提供し、クラスのクライアントが便利さと効率の上でトレードオフを選択できるようにする.すなわち、クライアントは、このように記述することを決定することができる.
Rational a, b, c, d, result;
...
result = a + b + c + d;            //     3     
                             //   operator+     1  

このように書きます.
result = a;                                  //      
result += b;                                 //      
result += c;                                 //      
result += d;                                 //      

前者は作成、debug、メンテナンスが比較的容易であり、80%の時間でその性能が受け入れられる(条項M 16を参照).後者はより効率的であり,アセンブリ言語プログラマーにとってより直感的であると推定される.2つのシナリオを提供することで、クライアント開発者は、個別の形式の代わりに効率の高いoperator付与形式を使用する権限を維持しながら、より読みやすい個別の形式のoperatorでコードとdebugコードを開発し、debugコードを開発することができます.またoperatorの付与形式に基づいて個別の形式を実現することで、クライアントがある形式から別の形式に切り替わると、操作の意味が変わらないことを保証できます.
最後にoperator単独形式の実装について述べる.operator+の実装を見てみましょう.
template<class T>
const T operator+(const T& lhs, const T& rhs)
{ return T(lhs) += rhs; }

式T(lhs)はTの
コピーコンストラクタ.lhsと同じ値の一時オブジェクトを作成します.この一時オブジェクトはrhsとともにoperator+=を呼び出すために使用され、操作の結果はoperator+から返されます.このコードはそんなに隠密に書かなくてもいいようです.このように書いたほうがいいのではないでしょうか.(
もしかしてさっき見た戻り値最適化を無駄に見たのか???
template<class T>
const T operator+(const T& lhs, const T& rhs)
{
  T result(lhs);                        //   lhs   result 
  return result += rhs;                 // rhs         
}

このテンプレートはほとんど前のプログラムと同じですが、それらの間には重要な違いがあります.
2番目のテンプレートには、名前付きオブジェクト、resultが含まれます.このネーミングオブジェクトはoperator+で戻り値最適化を使用できないことを意味します(条項M 20を参照).
第1の実装方法は、常に戻り値最適化を使用することができるので、コンパイラが最適化コードを生成する可能性が高くなります.
広告の事実は私に表現を指摘させました.
return T(lhs) += rhs;

ほとんどのコンパイラが望んでいる戻り値の最適化よりも複雑です.
上の最初の関数実装にも、名前付きオブジェクトresultを使用するために費やしたオーバーヘッドと同じように、一時的なオブジェクトオーバーヘッドがあります.ただし、ネーミングされていないオブジェクトは、ネーミングされたオブジェクトよりも履歴的に消去されやすいため、ネーミングされたオブジェクトと一時的なオブジェクトの間で選択する場合は、一時的なオブジェクトを使用するほうが便利です.名前付きオブジェクトよりもコストがかかりません.特に古いコンパイラを使用すると、コストが少なくなります. 
ここで説明するネーミングオブジェクト、ネーミングされていないオブジェクト、コンパイル最適化は興味深いですが、
主な点はoperatorの付与形式(operator+=)が単独形式(operator+)よりも効率的であることである.ライブラリ・プログラムの設計者として、両方を提供する必要があります.アプリケーションの開発者として、パフォーマンスを優先するときは、operator付与形式で個別の形式の代わりに使用することを考慮する必要があります.