Sobrecarga de operador: função de membro vs. função de não membro?

121

Eu li que um operador sobrecarregado declarado como função de membro é assimétrico porque pode ter apenas um parâmetro e o outro parâmetro passado automaticamente é o thisponteiro. Portanto, não existe um padrão para compará-los. Por outro lado, o operador sobrecarregado declarado como a friendé simétrico porque passamos dois argumentos do mesmo tipo e, portanto, eles podem ser comparados.

Minha pergunta é que, quando ainda posso comparar o lvalue de um ponteiro com uma referência, por que os amigos são preferidos? (usar uma versão assimétrica dá os mesmos resultados que simétrica) Por que os algoritmos STL usam apenas versões simétricas?

badmaash
fonte
11
Sua pergunta é realmente apenas sobre operadores binários. Nem todos os operadores sobrecarregados estão restritos a um único parâmetro. O operador () pode receber qualquer número de parâmetros. Os operadores unários, por outro lado, não podem ter parâmetros.
Charles Salvia
4
Este é um dos muitos tópicos abordados nas Perguntas frequentes sobre C ++: Sobrecarga de operador
Ben Voigt

Respostas:

148

Se você definir sua função sobrecarregada de operador como função de membro, o compilador traduzirá expressões como s1 + s2em s1.operator+(s2). Isso significa que a função de membro sobrecarregada do operador é chamada no primeiro operando. É assim que funcionam as funções de membro!

Mas e se o primeiro operando não for uma classe? Há um grande problema se quisermos sobrecarregar um operador onde o primeiro operando não é um tipo de classe, digamos double. Então você não pode escrever assim 10.0 + s2. No entanto, você pode escrever função de membro com sobrecarga de operador para expressões como s1 + 10.0.

Para resolver esse problema de ordenação , definimos a função sobrecarregada do operador como friendSE ela precisa acessar os privatemembros. Faça-o friendSOMENTE quando precisar acessar membros privados. Caso contrário, simplesmente torne -a uma função não-membro não-amiga para melhorar o encapsulamento!

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
}

Leia estes:
Um pequeno problema de ordenação de operandos
Como funções de não membros melhoram o encapsulamento

Nawaz
fonte
2
"Faça isso friendapenas quando precisar acessar membros privados ... e quando você não tiver / estiver entediado de escrever acessadores, certo?
badmaash
4
@Abhi: Escolha sua escolha: Encapsulamento aprimorado vs hábito de escrita preguiçoso!
Nawaz
6
@matthias, nem todos os operadores são comutativos. Um exemplo simples é a/b.
edA-qa mort-ora-y
3
Uma maneira comum de evitar que seus operadores não membros exijam friendé implementá-los em termos dos operadores de atribuição de operação (que quase certamente serão membros públicos). Por exemplo, você pode definir T T::operator+=(const T &rhs)como membro e, em seguida, definir não membro T operator(T lhs, const T &rhs)como return lhs += rhs;. A função não membro deve ser definida no mesmo namespace da classe.
Adrian McCarthy
2
@ricky: Mas se o lhs é uma cópia (como está em meu comentário), então o fato de que o lhs muda não importa.
Adrian McCarthy
20

Não é necessariamente uma distinção entre friendsobrecargas de operador e sobrecargas de operador de função de membro, pois é entre sobrecargas de operador global e sobrecarga de operador de função de membro.

Uma razão para preferir um mundial sobrecarga de operador é se você quiser permitir que expressões onde o tipo de classe aparece no direito lado de um operador binário. Por exemplo:

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

Isso só funciona se houver uma sobrecarga de operador global para

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

Observe que a sobrecarga do operador global não precisa necessariamente ser uma friendfunção. Isso só é necessário se precisar de acesso a membros privados de Foo, mas nem sempre é o caso.

Independentemente disso, se Footivesse apenas uma sobrecarga de operador de função de membro, como:

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

... então, só poderíamos ter expressões em que uma Fooinstância apareça à esquerda do operador mais.

Charles Salvia
fonte
3
+1 para fazer a distinção entre funções de membro e funções de não membro, em vez de funções de membro e amigo. Acho que hoje diríamos "escopo global ou namespace".
Adrian McCarthy