オーバーロードする一般的な演算子
演算子のオーバーロードでの作業のほとんどは、ボイラープレートコードです。演算子は単なる構文上の糖なので、実際の作業は単純な関数によって行われる可能性があります(多くの場合、転送されます)。ただし、この定型コードを正しく取得することが重要です。失敗すると、オペレーターのコードがコンパイルされないか、ユーザーのコードがコンパイルされないか、ユーザーのコードが驚くほど動作します。
代入演算子
割り当てについては多くのことを言う必要があります。ただし、そのほとんどはGManの有名なコピーアンドスワップFAQですでに述べられているため、ここではそのほとんどを省略し、参照用に完全な代入演算子のみをリストします。
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
ビットシフト演算子(ストリームI / Oに使用)
ビットシフト演算子<<
とは>>
、Cから継承するビット操作関数のハードウェアインターフェイスで引き続き使用されていますが、ほとんどのアプリケーションでオーバーロードされたストリーム入力および出力演算子として普及しています。ビット操作演算子としてのオーバーロードのガイダンスについては、以下の2項算術演算子のセクションを参照してください。オブジェクトをiostreamで使用するときに独自のカスタム形式と解析ロジックを実装する場合は、続行します。
ストリーム演算子は、最も一般的にオーバーロードされた演算子の1つであり、構文がメンバーまたは非メンバーであるかどうかの制限を指定しない2項中置演算子です。それらは左引数を変更する(ストリームの状態を変更する)ため、経験則に従って、左オペランドの型のメンバーとして実装する必要があります。ただし、それらの左側のオペランドは標準ライブラリからのストリームであり、標準ライブラリによって定義されたほとんどのストリーム出力および入力演算子は実際にストリームクラスのメンバーとして定義されていますが、独自のタイプの出力および入力操作を実装すると、標準ライブラリのストリームタイプを変更できません。そのため、独自の型に対してこれらの演算子を非メンバー関数として実装する必要があります。2つの正規形は次のとおりです。
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
を実装する場合operator>>
、ストリーム自体の状態を手動で設定する必要があるのは、読み取り自体が成功した場合のみですが、期待どおりの結果は得られません。
関数呼び出し演算子
関数オブジェクト(ファンクターとも呼ばれる)の作成に使用される関数呼び出し演算子は、メンバー関数として定義する必要があるため、常にthis
メンバー関数の暗黙の引数を持っています。これ以外に、オーバーロードしてゼロを含む任意の数の追加の引数を取ることができます。
次に構文の例を示します。
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
使用法:
foo f;
int a = f("hello");
C ++標準ライブラリ全体で、関数オブジェクトは常にコピーされます。したがって、独自の関数オブジェクトは安価にコピーできます。関数オブジェクトがコピーにコストのかかるデータを絶対に使用する必要がある場合は、そのデータを別の場所に格納し、関数オブジェクトにそれを参照させることをお勧めします。
比較演算子
バイナリのインフィックス比較演算子は、経験則に従って、非メンバー関数として実装する必要があります1。単項接頭辞の否定!
は、(同じルールに従って)メンバー関数として実装する必要があります。(ただし、通常、オーバーロードすることはお勧めしません。)
標準ライブラリのアルゴリズム(例:)std::sort()
とタイプ(例std::map
:)は、常に存在するoperator<
ことだけを期待します。ただし、同じタイプのユーザーは、他のすべての演算子も存在することを期待しているため、を定義する場合operator<
は、演算子のオーバーロードの3番目の基本ルールに従い、他のすべてのブール比較演算子も定義してください。それらを実装する標準的な方法はこれです:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
ここで注意すべき重要なことは、これらの演算子の2つだけが実際に何かを実行し、他の演算子は実際の作業を実行するためにこれら2つの演算子のいずれかに引数を転送するだけであるということです。
残りの2項ブール演算子(||
、&&
)をオーバーロードするための構文は、比較演算子の規則に従います。ただし、これらの2の合理的なユースケースを見つけることはほとんどありません。
1 すべての経験則と同様に、これを破る理由もある場合があります。その場合、メンバー関数ではとなる2項比較演算子の左側のオペランドも*this
である必要があることを忘れないでくださいconst
。したがって、メンバー関数として実装された比較演算子には、次のシグネチャが必要です。
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(const
最後のに注意してください。)
2 組み込みバージョンのショートカットセマンティクス||
を&&
使用することに注意してください。ユーザーが定義したもの(メソッド呼び出しの構文上の砂糖のため)は、ショートカットセマンティクスを使用しないでください。ユーザーは、これらの演算子がショートカットセマンティクスを持つことを期待し、それらのコードはそれに依存する可能性があるため、絶対に定義しないことを強くお勧めします。
算術演算子
単項算術演算子
単項インクリメントおよびデクリメント演算子には、プレフィックスとポストフィックスの両方の種類があります。互いに区別するために、postfixバリアントは追加のダミーのint引数を取ります。インクリメントまたはデクリメントをオーバーロードする場合は、必ずプレフィックスバージョンとポストフィックスバージョンの両方を実装してください。以下は、インクリメントの標準的な実装です。デクリメントは同じルールに従います。
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
postfixバリアントは接頭辞に関して実装されることに注意してください。また、postfixは余分なコピーを行うことに注意してください。2
単項マイナスとプラスのオーバーロードはあまり一般的ではなく、おそらく回避するのが最善です。必要に応じて、メンバー関数としてオーバーロードする必要があります。
2 接尾辞バリアントは接頭辞バリアントよりも多くの作業を行うため、使用する効率が悪いことにも注意してください。これは、一般に接尾辞の増分よりも接頭辞の増分を優先する良い理由です。コンパイラーは通常、組み込み型の後置インクリメントの追加作業を最適化できますが、ユーザー定義型(リストイテレーターのように無邪気に見えるもの)に対しては同じことができない場合があります。に慣れると、が組み込み型ではない場合に代わりにi++
行うことを覚えるのが非常に難しくなります(さらに、型を変更するときにコードを変更する必要があるため)、常に習慣をつけることをお勧めします接頭辞が明示的に必要でない限り、接頭辞の増分を使用します。++i
i
二項算術演算子
あなたが提供する場合:バイナリ算術演算子については、第3の基本ルール演算子オーバーロード従うことを忘れないでください+
、また提供+=
、あなたが提供する場合は-
省略しないでください、-=
などアンドリュー・ケーニッヒは、化合物譲渡することを最初に観察していたと言われています演算子は、対応する非複合のベースとして使用できます。すなわち、オペレータがさ+
の点で実現される+=
、-
という点で実現されている-=
等
経験法則によれば+
、そのコンパニオンは非メンバー+=
である必要がありますが、左側の引数を変更するそれらの複合割り当ての対応物(など)はメンバーである必要があります。次に、+=
およびのコード例を+
示します。他の2項算術演算子も同じ方法で実装する必要があります。
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
参照ごとに結果を返し、結果operator+
のコピーを返します。もちろん、参照を返す方が通常はコピーを返すよりも効率的ですが、の場合operator+
、コピーを回避する方法はありません。を記述する場合a + b
、結果は新しい値であることが期待されます。そのため、新しい値operator+
を返す必要があります。3operator+
は、その左オペランドをconst参照ではなくコピーで取得する
ことにも注意してください。この理由は、operator=
コピーごとに議論をする理由と同じです。
ビット操作演算子~
&
|
^
<<
>>
は、算術演算子と同じ方法で実装する必要があります。ただし、(オーバーロード<<
および>>
出力と入力を除いて)これらをオーバーロードするための合理的なユースケースはほとんどありません。
3 繰り返しますが、これから得られる教訓a += b
は、一般的には、より効率的でa + b
あり、可能であれば推奨することです。
配列の添え字
配列の添字演算子は、クラスメンバーとして実装する必要がある2項演算子です。キーによるデータ要素へのアクセスを許可するコンテナのようなタイプに使用されます。これらを提供する正規の形式は次のとおりです。
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
クラスのユーザーがによって返されるデータ要素を変更できないようにするoperator[]
場合を除き(この場合、非constバリアントを省略できます)、演算子の両方のバリアントを常に提供する必要があります。
value_typeが組み込み型を参照することがわかっている場合、演算子のconstバリアントは、const参照の代わりにコピーを返す方が適切です。
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
ポインタのような型の演算子
独自のイテレーターまたはスマートポインターを定義するには、単項前置参照解除演算子*
とバイナリ中置ポインターメンバーアクセス演算子をオーバーロードする必要があります->
。
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
これらも、ほとんど常にconstバージョンと非constバージョンの両方が必要になることに注意してください。ため->
た場合、オペレータ、value_type
であるclass
(又はstruct
又はunion
タイプ)、他はoperator->()
れるまで、再帰的に呼び出されoperator->()
、非クラス型の値を返します。
単項アドレス演算子はオーバーロードしないでください。
以下のためのoperator->*()
参照この質問を。これはめったに使用されないため、めったにオーバーロードされません。実際、イテレータでさえそれをオーバーロードしません。
進んで変換演算子