二項演算子をメンバー関数としてオーバーロードすると、最終的に非対称になります。左のオペランドは、演算子がオーバーロードされる正確な型である必要がありますが、右のオペランドは、正しい型に変換できるものであれば何でもかまいません。
非メンバー関数で演算子をオーバーロードすると、両方のオペランドを変換して正しい型を取得できます。
2番目のポイントとして持っているものは、同じポイントの具体的な例のように見えますが、実際にはまったく別のものではありません. これが私が話していることの具体的な例です:
class Integer {
int val;
public:
Integer(int i) : val(i) {}
operator int() { return val; }
// Integer operator+(Integer const &other) const { return Integer(val + other.val); }
friend Integer operator+(Integer const &a, Integer const &b) {
return Integer(a.val + b.val);
}
};
int main() {
Integer x(1);
Integer y = x + 2; // works with either operator overload because x is already an Integer
Integer z = 2 + x; // works with global overload, but not member overload because 2 isn't an Integer, but can be converted to an Integer.
return 0;
}
friend
また、関数の定義が のクラス定義内にある場合でもInteger
、それがフレンドとして宣言されているという事実は、それがメンバー関数ではないことを意味することに注意してください。それを宣言するfriend
と、メンバーではなくグローバル関数になります。
結論: このようなオーバーロードは、通常、メンバー関数ではなく、フリー関数として実行する必要があります。正しく (大幅に) 動作するオペレーターをユーザーに提供することは、「よりオブジェクト指向」などの理論的な考慮事項よりも重要です。オペレーターの実装を仮想化する必要がある場合など、必要に応じて、実際の作業を行う (おそらく仮想の) メンバー関数を提供する 2 ステップ バージョンを実行できますが、オーバーロード自体は自由な関数であり、左オペランドでそのメンバー関数を呼び出します。これのかなり一般的な例の 1 つはoperator<<
、階層のオーバーロードです。
class base {
int x;
public:
std::ostream &write(std::ostream &os) const {
return os << x;
}
};
class derived : public base {
int y;
public:
std::ostream &write(std::ostream &os) const {
return (base::write(os) << y);
}
};
std::ostream &operator<<(std::ostream &os, base const &b) {
return b.write(os);
}
これは、取得するためのオペレーターの通常の特性をあきらめることなく、ポリモーフィックな実装 (および必要に応じて基本クラスの保護されたメンバーへのアクセス) の両方をサポートします。
二項演算子をフリー関数としてオーバーロードする主な例外は、代入演算子 ( operator=
、operator+=
、operator-=
などoperator*=
) です。変換により一時オブジェクトが生成されますが、これに代入することはできません。そのため、この特定のケースでは、オーバーロードは (実際にはそうである必要があります) 代わりにメンバー関数にする必要があります。