Surcharge des opérateurs: fonction membre vs fonction non membre?

121

J'ai lu qu'un opérateur surchargé déclaré comme fonction membre est asymétrique car il ne peut avoir qu'un seul paramètre et l'autre paramètre passé automatiquement est le thispointeur. Il n'existe donc aucune norme pour les comparer. D'autre part, l'opérateur surchargé déclaré comme a friendest symétrique car nous passons deux arguments du même type et par conséquent, ils peuvent être comparés.

Ma question est la suivante: lorsque je peux encore comparer la valeur d'un pointeur à une référence, pourquoi les amis sont-ils préférés? (l'utilisation d'une version asymétrique donne les mêmes résultats qu'une version symétrique) Pourquoi les algorithmes STL n'utilisent-ils que des versions symétriques?

badmaash
la source
11
Votre question ne concerne en réalité que les opérateurs binaires. Tous les opérateurs surchargés ne sont pas limités à un seul paramètre. L'opérateur () peut prendre n'importe quel nombre de paramètres. Les opérateurs unaires, par contre, ne peuvent avoir aucun paramètre.
Charles Salvia
4
C'est l'un des nombreux sujets abordés dans la FAQ C ++: Surcharge des opérateurs
Ben Voigt

Réponses:

148

Si vous définissez votre fonction surchargée d'opérateur en tant que fonction membre, le compilateur traduit des expressions comme s1 + s2en s1.operator+(s2). Cela signifie que la fonction membre surchargée par l'opérateur est appelée sur le premier opérande. C'est ainsi que fonctionnent les fonctions membres!

Mais que faire si le premier opérande n'est pas une classe? Il y a un problème majeur si nous voulons surcharger un opérateur où le premier opérande n'est pas un type de classe, disons plutôt double. Vous ne pouvez donc pas écrire comme ça 10.0 + s2. Cependant, vous pouvez écrire une fonction membre surchargée d'opérateur pour des expressions telles que s1 + 10.0.

Pour résoudre ce problème d' ordre , nous définissons la fonction surchargée d'opérateur comme friendSI elle a besoin d'accéder aux privatemembres. Faites-le friendUNIQUEMENT lorsqu'il a besoin d'accéder aux membres privés. Sinon, faites simplement en sorte que ce soit une fonction non-membre ami pour améliorer l' encapsulation!

class Sample
{
 public:
    Sample operator + (const Sample& op2); //works with s1 + s2
    Sample operator + (double op2); //works with s1 + 10.0

   //Make it `friend` only when it needs to access private members. 
   //Otherwise simply make it **non-friend non-member** function.
    friend Sample operator + (double op1, const Sample& op2); //works with 10.0 + s2
}

Lisez ceci:
Un léger problème d'ordre dans les opérandes
Comment les fonctions non membres améliorent l'encapsulation

Nawaz
la source
2
"Faites-le friendseulement quand il a besoin d'accéder aux membres privés..et quand vous n'avez pas / êtes ennuyé d'écrire des accesseurs, non?
badmaash
4
@Abhi: Choisissez votre choix: Encapsulation améliorée vs habitude d'écriture paresseuse!
Nawaz
6
@matthias, tous les opérateurs ne sont pas commutatifs. Un exemple simple est a/b.
edA-qa mort-ora-y
3
Un moyen courant d'éviter de forcer vos opérateurs non membres à exiger friendest de les implémenter en termes d'opérateurs d'affectation d'opération (qui seront presque certainement des membres publics). Par exemple, vous pouvez définir T T::operator+=(const T &rhs)comme membre, puis définir non-membre T operator(T lhs, const T &rhs)comme return lhs += rhs;. La fonction non membre doit être définie dans le même espace de noms que la classe.
Adrian McCarthy
2
@ricky: Mais si le lhs est une copie (comme c'est le cas dans mon commentaire), alors le fait que le lhs change n'a pas d'importance.
Adrian McCarthy
20

Ce n'est pas nécessairement une distinction entre friendles surcharges d'opérateurs et les surcharges d'opérateurs de fonctions membres, comme c'est le cas entre les surcharges d'opérateurs globaux et les surcharges d'opérateurs de fonctions membres.

Une raison de préférer une surcharge d'opérateur global est si vous souhaitez autoriser les expressions où le type de classe apparaît sur le côté droit d'un opérateur binaire. Par exemple:

Foo f = 100;
int x = 10;
cout << x + f;

Cela ne fonctionne que s'il y a une surcharge d'opérateur global pour

Opérateur Foo + (int x, const Foo & f);

Notez que la surcharge d'opérateur global n'a pas nécessairement besoin d'être une friendfonction. Cela n'est nécessaire que s'il a besoin d'accéder à des membres privés de Foo, mais ce n'est pas toujours le cas.

Quoi qu'il en soit, si Fooseulement il y avait une surcharge d'opérateur de fonction membre, comme:

class Foo
{
  ...
  Foo operator + (int x);
  ...
};

... alors nous ne pourrions avoir d'expressions où une Fooinstance apparaît à gauche de l'opérateur plus.

Charles Salvia
la source
3
+1 pour faire la distinction entre les fonctions membres et les fonctions non membres plutôt que les fonctions membres et amis. Je suppose qu'aujourd'hui, nous dirions "portée globale ou espace de noms".
Adrian McCarthy