More Effective C++22:個別形式の代わりに演算子の付与形式を考慮


ほとんどのプログラマーは、コードをこのように書くことができると考えています.
x = x + y; x = x - y;

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

しかし,xとyがユーザ定義のタイプであれば,それを確保することはできない.operator+operator=operator+=の間には何の関係もありません.そのため、この3つのoperatorを同時に存在させ、あなたが望んでいる関係を持つには、自分で実現しなければなりません.同様に、operator-、*、/、なども同様です.operatorの賦値形態(例えば、operator+=)とoperatorの個別形態(例えば、operator+)との間に正常な関係があることを確認するには、後者が前者に従って、例えば、
class Rational 
{ 
public: 
	... 
	Rational& operator+=(const Rational& rhs); 
	Rational& operator-=(const Rational& rhs); 
};
const Rational operator+(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs) += rhs;
}

const Rational operator-(const Rational& lhs, const Rational& rhs) 
{ 
	return Rational(lhs) -= rhs; 
}

この例では、ゼロからoperator+=および-=が実装され、operator+およびoperator-は前述の関数を呼び出すことによって独自の機能を提供する.この設計手法を用いて,operato rの付与形式を維持するだけでよい.
すべての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; 
} 
...

このように書くのは確かにいいですが、これまで効率の問題を考えていませんでした.ここでは3つの効率の問題を指摘する価値があります.
1.全体として、operatorの付加価値形式は、個別形式よりも効率的である.なぜなら、個別形式は新しいオブジェクトを返し、一時的なオブジェクトの構造および解放にいくつかのオーバーヘッドがかかるからである.operatorの付与形式は結果を左のパラメータに書き込むので,operatorの戻り値を収容するために一時オブジェクトを生成する必要はない.
2.operatorの付与形式を提供すると同時に、その標準形式を提供し、クラスのクライアントが便利さと効率の上でトレードオフを選択できるようにする.すなわち、クライアントは、このように記述することを決定することができる.
Rational a, b, c, d, result;
result = a + b + c + d;//      3      


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

前者は作成、debug、メンテナンスが比較的容易であり、80%の時間でその性能が受け入れられる.後者はより効率的であり,アセンブリ言語プログラマーにとってより直感的であると推定される.2つのスキームを提供することによって、クライアント開発者は、単独の形式の代わりに効率の高いoperatorの付与形式を使用する権利を維持しながら、より読みやすい単独の形式のdebugでコードを開発し、operatorコードを開発することができます.
3.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+で戻り値を使用して最適化できないことを意味します.第1の実装方法は、常に戻り値最適化を使用することができるので、コンパイラが最適化コードを生成する可能性が高くなります.
return T(lhs) += rhs;

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