Sobrecarga del operador: ¿función miembro frente a función no miembro?

121

Leí que un operador sobrecargado declarado como función miembro es asimétrico porque solo puede tener un parámetro y el otro parámetro pasado automáticamente es el thispuntero. Entonces no existe un estándar para compararlos. Por otro lado, el operador sobrecargado declarado como a friendes simétrico porque pasamos dos argumentos del mismo tipo y, por lo tanto, se pueden comparar.

Mi pregunta es que cuando todavía puedo comparar el valor l de un puntero con una referencia, ¿por qué se prefieren los amigos? (el uso de una versión asimétrica da los mismos resultados que la simétrica) ¿Por qué los algoritmos STL usan solo versiones simétricas?

badmaash
fuente
11
Su pregunta es realmente solo sobre operadores binarios. No todos los operadores sobrecargados están restringidos a un solo parámetro. El operador () puede tomar cualquier número de parámetros. Los operadores unarios, por otro lado, no pueden tener ningún parámetro.
Charles Salvia
4
Este es uno de los muchos temas que se tratan en las Preguntas frecuentes de C ++: Sobrecarga del operador
Ben Voigt

Respuestas:

148

Si define la función sobrecargada de su operador como función miembro, el compilador traduce expresiones como s1 + s2en s1.operator+(s2). Eso significa que la función miembro sobrecargada del operador se invoca en el primer operando. ¡Así es como funcionan las funciones miembro!

Pero, ¿y si el primer operando no es una clase? Hay un problema importante si queremos sobrecargar un operador donde el primer operando no es un tipo de clase, más bien digamos double. Entonces no puedes escribir así 10.0 + s2. Sin embargo, puede escribir la función miembro sobrecargada del operador para expresiones como s1 + 10.0.

Para resolver este problema de ordenación , definimos la función sobrecargada del operador como friendSI necesita acceder a los privatemiembros. Hágalo friendSOLO cuando necesite acceder a miembros privados. De lo contrario, simplemente conviértalo en una función no miembro no amiga para mejorar la encapsulación.

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
}

Lea esto:
Un pequeño problema de orden en operandos
Cómo las funciones que no son miembros mejoran la encapsulación

Nawaz
fuente
2
"Hágalo friendsolo cuando necesite acceder a miembros privados ... y cuando no tenga / esté aburrido de escribir
descriptores de
4
@Abhi: Elige tu elección: Encapsulación mejorada frente al hábito de escritura perezosa
Nawaz
6
@matthias, no todos los operadores son conmutativos. Un ejemplo simple es a/b.
edA-qa mort-ora-y
3
Una forma común de evitar que sus operadores no miembros lo requieran friendes implementarlos en términos de operadores de asignación de operación (que casi con certeza serán miembros públicos). Por ejemplo, puede definir T T::operator+=(const T &rhs)como miembro y luego definir no miembro T operator(T lhs, const T &rhs)como return lhs += rhs;. La función no miembro debe definirse en el mismo espacio de nombres que la clase.
Adrian McCarthy
2
@ricky: Pero si lhs es una copia (como está en mi comentario), entonces el hecho de que lhs cambie no importa.
Adrian McCarthy
20

No es necesariamente una distinción entre las friendsobrecargas del operador y las sobrecargas del operador de la función miembro, ya que lo es entre las sobrecargas del operador global y las sobrecargas del operador de la función miembro.

Una razón para preferir una sobrecarga de operadores global es si desea permitir expresiones donde el tipo de clase aparece en el lado derecho de un operador binario. Por ejemplo:

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

Esto solo funciona si hay una sobrecarga de operador global para

Operador Foo + (int x, const Foo & f);

Tenga en cuenta que la sobrecarga del operador global no tiene por qué ser necesariamente una friendfunción. Esto solo es necesario si necesita acceso a miembros privados de Foo, pero no siempre es así.

Independientemente, si Foosolo tuviera una sobrecarga de operador de función miembro, como:

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

... entonces solo podríamos tener expresiones donde Fooaparezca una instancia a la izquierda del operador más.

Carlos Salvia
fuente
3
+1 para hacer la distinción entre funciones de miembros y funciones de no miembros en lugar de funciones de miembros y amigos. Supongo que hoy diríamos "ámbito global o de espacio de nombres".
Adrian McCarthy