web-dev-qa-db-fra.com

différence entre opérateur global et opérateur membre

Existe-t-il une différence entre définir un opérateur global prenant deux références pour une classe et définir un opérateur membre prenant uniquement le bon opérande?

Global:

class X
{
public:
    int value;
};

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

Membre:

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

Une des raisons d'utiliser des opérateurs non membres (généralement déclarés comme amis) est que le côté gauche est celui qui effectue l'opération. Obj::operator+ convient pour:

obj + 2

mais pour:

2 + obj

ça ne marchera pas. Pour cela, vous avez besoin de quelque chose comme:

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

Votre option la plus intelligente est d’en faire une fonction friend .

Comme JaredPar le mentionne, l'implémentation globale ne peut pas accéder aux membres des classes protégées et privées, mais la fonction membre pose également un problème.

C++ autorisera les conversions implicites des paramètres de fonction, mais pas une conversion implicite dethis.

S'il existe des types pouvant être convertis en classe X:

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


X x1, x2;
Y y1, y2;

Seules certaines des expressions suivantes seront compilées avec une fonction membre.

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

La solution, pour obtenir le meilleur des deux mondes, consiste à implémenter cela en ami:

class X
{
    int value;

public:

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

Pour résumer la réponse de Codebender:

Les opérateurs membres ne sont pas symétriques. Le compilateur ne peut pas effectuer le même nombre d'opérations avec les opérateurs gauche et droit.

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

Dans le code ci-dessus, la compilation échouera si l'opérateur est une fonction membre et fonctionnera normalement si l'opérateur est une fonction libre.

En général, un modèle courant implémente les opérateurs qui doivent être des fonctions membres en tant que membres et le reste en tant que fonctions libres qui délèguent aux opérateurs membres:

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

Il y a au moins une différence. Un opérateur membre est soumis à des modificateurs d'accès et peut être public, protégé ou privé. Une variable membre globale n'est pas soumise à des restrictions de modificateur d'accès. 

Ceci est particulièrement utile lorsque vous souhaitez désactiver certains opérateurs tels que l'affectation

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

Vous pouvez obtenir le même effet en ayant un opérateur global déclaré uniquement. Mais cela entraînerait une erreur de lien par rapport à une erreur de compilation (nipick: oui, cela entraînerait une erreur de lien dans Foo)

5
JaredPar

Voici un exemple réel où la différence n'est pas évidente:

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

En effet, le premier formulaire utilise l'opérateur d'égalité de la classe de base, ce qui permet de convertir son côté droit en Base. Mais l'opérateur d'égalité de classe dérivée ne peut pas faire l'inverse, d'où l'erreur.

Si l'opérateur de la classe de base était plutôt déclaré comme une fonction globale, les deux exemples fonctionneraient (l'absence d'opérateur d'égalité dans la classe dérivée résoudrait également le problème, mais il est parfois nécessaire).

0
riv