web-dev-qa-db-ja.com

グローバル演算子とメンバー演算子の違い

クラスの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;
    };
}
39
Vargas

非メンバー演算子(通常はフレンドとして宣言されている)を使用する理由の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) { ... }
47
Tim Sylvester

あなたの最も賢いオプションは、それをフレンド関数にすることです。

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;
    };
};
9
Drew Dormann

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内でリンクエラーが発生します)

5
JaredPar

違いが明らかでない実際の例を次に示します。

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に変換されます。しかし、派生クラスの等価演算子はその逆を行うことができないため、エラーが発生します。

基本クラスの演算子が代わりにグローバル関数として宣言された場合、両方の例が機能します(派生クラスに等価演算子がないことでも問題が修正されますが、必要になる場合もあります)。

0
riv