クラスの2つの参照を使用するグローバル演算子の定義と、正しいオペランドのみを使用するメンバー演算子の定義に違いはありますか?
グローバル:
class X
{
public:
int value;
};
bool operator==(X& left, X& right)
{
return left.value == right.value;
};
メンバー:
class X
{
int value;
bool operator==( X& right)
{
return value == right.value;
};
}
非メンバー演算子(通常はフレンドとして宣言されている)を使用する理由の1つは、左側が演算を行う演算子であるためです。 Obj::operator+
は次の場合に問題ありません。
obj + 2
しかし:
2 + obj
動作しません。これには、次のようなものが必要です。
class Obj
{
friend Obj operator+(const Obj& lhs, int i);
friend Obj operator+(int i, const Obj& rhs);
};
Obj operator+(const Obj& lhs, int i) { ... }
Obj operator+(int i, const Obj& rhs) { ... }
あなたの最も賢いオプションは、それをフレンド関数にすることです。
JaredParが言及しているように、グローバル実装は保護クラスおよびプライベートクラスのメンバーにアクセスできませんが、メンバー関数にも問題があります。
C++では、関数パラメーターの暗黙的な変換は許可されますが、this
の暗黙的な変換は許可されません。
Xクラスに変換できるタイプが存在する場合:
class Y
{
public:
operator X(); // Y objects may be converted to X
};
X x1, x2;
Y y1, y2;
次の式の一部だけがメンバー関数でコンパイルされます。
x1 == x2; // Compiles with both implementations
x1 == y1; // Compiles with both implementations
y1 == x1; // ERROR! Member function can't convert this to type X
y1 == y2; // ERROR! Member function can't convert this to type X
両方の世界を最大限に活用するための解決策は、これを友達として実装することです。
class X
{
int value;
public:
friend bool operator==( X& left, X& right )
{
return left.value == right.value;
};
};
Codebenderの答えをまとめると:
メンバー演算子は対称的ではありません。コンパイラは、左側と右側の演算子で同じ数の演算を実行できません。
struct Example
{
Example( int value = 0 ) : value( value ) {}
int value;
Example operator+( Example const & rhs ); // option 1
};
Example operator+( Example const & lhs, Example const & rhs ); // option 2
int main()
{
Example a( 10 );
Example b = 10 + a;
}
上記のコードでは、演算子がメンバー関数の場合はコンパイルに失敗しますが、演算子が自由関数の場合は期待どおりに機能します。
一般に、一般的なパターンは、メンバー関数である必要がある演算子をメンバーとして実装し、残りをメンバー演算子に委任するフリー関数として実装することです。
class X
{
public:
X& operator+=( X const & rhs );
};
X operator+( X lhs, X const & rhs )
{
lhs += rhs; // lhs was passed by value so it is a copy
return lhs;
}
少なくとも1つの違いがあります。メンバーオペレーターはアクセス修飾子の対象であり、パブリック、保護、またはプライベートにすることができます。グローバルメンバー変数は、アクセス修飾子の制限を受けません。
これは、割り当てなどの特定の演算子を無効にする場合に特に役立ちます。
class Foo {
...
private:
Foo& operator=(const Foo&);
};
グローバルオペレーターのみを宣言することで、同じ効果を得ることができます。しかし、リンクエラーとコンパイルエラーのどちらが発生するか(ニピック:はい、Foo内でリンクエラーが発生します)
違いが明らかでない実際の例を次に示します。
class Base
{
public:
bool operator==( const Base& other ) const
{
return true;
}
};
class Derived : public Base
{
public:
bool operator==( const Derived& other ) const
{
return true;
}
};
Base() == Derived(); // works
Derived() == Base(); // error
これは、最初の形式が基底クラスの等価演算子を使用しているためです。これにより、右側がBase
に変換されます。しかし、派生クラスの等価演算子はその逆を行うことができないため、エラーが発生します。
基本クラスの演算子が代わりにグローバル関数として宣言された場合、両方の例が機能します(派生クラスに等価演算子がないことでも問題が修正されますが、必要になる場合もあります)。