+, ==, << などの演算子を、自作クラスに対しても動作させるのが演算子オーバーロードです。std::string s1 + s2、std::cout << x、v1 == v2 ― すべてこの仕組み。「演算子 = 関数名が記号」だと気づけば怖くありません。ただし濫用するとコードが読めなくなるので、使いどころも学びます。
operator+ など)+ や == を定義できるoperator<< は必ず非メンバで書くoperator<=>(宇宙船演算子)operator bool()2D ベクトルの足し算を + で書けるようにしてみましょう。
operator+ という関数名が記号の関数a + b は a.operator+(b) の糖衣構文Q. なんでもかんでも + にできるの?
→ 技術的には可能ですが、意味が自然な場合だけにするのが鉄則。数学的なベクトル・行列・複素数、あるいは文字列連結、のような自然な解釈がある演算子だけ。Dog + Cat のように意味不明な組み合わせは避けます。
Q. オペレータの優先順位は変えられる?
→ 変えられません。 + と * の優先順位は組み込み型と同じ。
Q. 新しい演算子(例:** で冪乗)は作れる?
→ 作れません。 既存の演算子だけ再定義できます。新しい記号は power(a, b) のような普通の関数にします。
C++ の演算子はすべて関数呼び出しに展開されます。
| 書いた式 | 実際に呼ばれる関数 |
|---|---|
a + b | a.operator+(b) または operator+(a, b) |
a == b | a.operator==(b) または operator==(a, b) |
a < b | a.operator<(b) または operator<(a, b) |
++a (前置) | a.operator++() |
a++ (後置) | a.operator++(int)(int はダミー) |
std::cout << a | operator<<(std::cout, a) |
a[i] | a.operator[](i) |
a() | a.operator()() |
つまり演算子オーバーロードとは「これらの特殊な名前の関数を自作クラスに定義する」ことにすぎません。
演算子はメンバ関数としても非メンバ(フリー関数)としても書けます。
原則:「対称」な演算子(+, -, ==)は非メンバ、「自分を変える」演算子(+=, ++)はメンバにするのが Scott Meyers の推奨:
+ - == < << ― 特に左右の型の入れ替え(2 * vec vs vec * 2)を両方サポートしたい場合= += ++ -- [] () ― 自身を変更する/必ず左辺がそのクラスであるものこうすると「+= のロジックは 1 箇所」にまとまり、a + b と a += b の挙動が必ず一致します。
STL コンテナ(std::sort, std::set, std::map)で使うためには < と == がよく必要になります。
operator<=> を 1 つ定義すると、6 個の比較演算子(<, <=, >, >=, ==, !=)が自動生成されます。ただし本サイトは C++17 基準なので、従来の書き方で進めます。
operator<< ― 出力演算子std::cout << p; と書けるようにする。非メンバで書くのが必須(左辺が std::ostream で変更できないため)。
ポイントは 3 つ:
std::ostream&std::ostream&(cout << a << b のチェーンのため)+ で連結)< で順序)operator<< はデバッグのためにほぼ必須operator())+ のどちらかだけ実装(v*2 OK / 2*v NG など、非対称は避ける)a + b == b + a が直感的に成り立つか?(可換性)a == b と !(a != b) が同じ?(整合性)a < b と b > a が同じ?次の演算子は絶対にオーバーロードできない(言語規格で禁止):
::(スコープ解決).(メンバアクセス).*(メンバポインタ経由のアクセス)?:(三項演算子)sizeof, typeid, alignof演算子オーバーロードを 4 問で確認。
a + b という式が実際に呼び出すのは?+ なら operator+。メンバ関数として定義されていれば a.operator+(b)、非メンバなら operator+(a, b) が呼ばれます。operator<<(std::cout << obj)は、メンバ関数・非メンバ関数のどちらで書くべき?operator<< は左辺が std::ostream(こちらは変更できない)なので、メンバ関数としては追加できません。必ず非メンバで書く必要があります。**(累乗)を自作クラスに追加したい。可能?pow(a, b) や a.pow(b) のような普通の関数にします。a + b と a += b; に対して演算子を定義するとき、推奨される実装順は?+=(自身を変更)を基礎にして + を実装するのがモダン C++ の定石:Vec operator+(Vec a, const Vec& b) { a += b; return a; }+ と += の挙動が必ず一致し、コードの重複もなくなります。