difference between global operator and member oper

2020-01-29 04:30发布

问题:

Is there a difference between defining a global operator that takes two references for a class and defining a member operator that takes only the right operand?

Global:

class X
{
public:
    int value;
};

bool operator==(X& left, X& right) 
{
    return left.value == right.value;
};

Member:

class X
{
    int value;
    bool operator==( X& right) 
    {
        return value == right.value;
    };
}

回答1:

One reason to use non-member operators (typically declared as friends) is because the left-hand side is the one that does the operation. Obj::operator+ is fine for:

obj + 2

but for:

2 + obj

it won't work. For this, you need something like:

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) { ... }


回答2:

Your smartest option is to make it a friend function.

As JaredPar mentions, the global implementation cannot access protected and private class members, but there's a problem with the member function too.

C++ will allow implicit conversions of function parameters, but not an implicit conversion of this.

If types exist that can be converted to your X class:

class Y
{
public:
    operator X();  // Y objects may be converted to X
};


X x1, x2;
Y y1, y2;

Only some of the following expressions will compile with a member function.

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

The solution, to get the best of both worlds, is to implement this as a friend:

class X
{
    int value;

public:

    friend bool operator==( X& left, X& right ) 
    {
        return left.value == right.value;
    };
};


回答3:

To sum up to the answer by Codebender:

Member operators are not symmetric. The compiler cannot perform the same number of operations with the left and right hand side operators.

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;
}

In the code above will fail to compile if the operator is a member function while it will work as expected if the operator is a free function.

In general a common pattern is implementing the operators that must be member functions as members and the rest as free functions that delegate on the member operators:

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;
}


回答4:

There is at least one difference. A member operator is subject to access modifiers and can be public, protected or private. A global member variable is not subject to access modifier restrictions.

This is particularly helpful when you want to disable certain operators like assignment

class Foo { 
  ...
private:
  Foo& operator=(const Foo&); 
};

You could achieve the same effect by having a declared only global operator. But it would result in a link error vs. a compile error (nipick: yes it would result in a link error within Foo)



回答5:

Here's a real example where the difference isn't obvious:

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

This is because the first form uses equality operator from base class, which can convert its right hand side to Base. But the derived class equality operator can't do the opposite, hence the error.

If the operator for the base class was declared as a global function instead, both examples would work (not having an equality operator in derived class would also fix the issue, but sometimes it is needed).