Eu não entendo os argumentos contra sobrecarga de operadores [closed]

82

Acabei de ler um dos artigos de Joel nos quais ele diz:

In general, I have to admit that I’m a little bit scared of language features that hide things. When you see the code

i = j * 5;

… in C you know, at least, that j is being multiplied by five and the results stored in i.

But if you see that same snippet of code in C++, you don’t know anything. Nothing. The only way to know what’s really happening in C++ is to find out what types i and j are, something which might be declared somewhere altogether else. That’s because j might be of a type that has operator* overloaded and it does something terribly witty when you try to multiply it.

(Ênfase minha.) Medo de recursos de linguagem que escondem as coisas? Como você pode ter medo disso? Não está escondendo coisas (também conhecidas como abstração ) uma das idéias-chave da programação orientada a objetos? Toda vez que você chama um método a.foo(b) , você não tem ideia do que isso pode fazer. Você precisa descobrir quais são os tipos a e b , algo que pode ser declarado em outro lugar. Então, devemos acabar com a programação orientada a objetos, porque ela esconde coisas demais do programador?

E como j * 5 é diferente de j.multiply(5) , que talvez você tenha que escrever em um idioma que não ofereça suporte à sobrecarga do operador? Novamente, você teria que descobrir o tipo de j e espiar dentro do método multiply , porque e eis que j pode ser de um tipo que possui um método multiply que faz algo terrivelmente espirituoso. / p>

"Muahaha, sou um mau programador que nomeia um método multiply , mas o que ele realmente faz é totalmente obscuro e não intuitivo e não tem absolutamente nada a ver com a multiplicação de coisas." Esse é um cenário que devemos levar em consideração ao projetar uma linguagem de programação? Então, temos que abandonar os identificadores das linguagens de programação, alegando que eles podem ser enganosos!

Se você quiser saber o que um método faz, você pode consultar a documentação ou dar uma olhada na implementação. A sobrecarga do operador é apenas um açúcar sintático e não vejo como isso muda o jogo.

Por favor, me esclareça.

    
por fredoverflow 10.12.2010 / 12:45
fonte

15 respostas

32

A abstração "oculta" o código, para que você não precise se preocupar com o funcionamento interno e, muitas vezes, para não modificá-lo, mas a intenção não é impedi-lo de analisá-lo. Nós apenas fazemos suposições sobre os operadores e, como o Joel disse, pode ser em qualquer lugar. Ter um recurso de programação que requer que todos os operadores sobrecarregados sejam estabelecidos em um local específico pode ajudar a encontrá-los, mas não tenho certeza se isso facilita o uso.

Eu não vejo fazer * fazer algo que não se assemelhe a multiplicação melhor do que uma função chamada Get_Some_Data que exclui dados.

    
por 10.12.2010 / 13:36
fonte
18

IMHO, recursos de linguagem como sobrecarga de operador dão ao programador mais potência. E, como todos sabemos, com grande poder vem uma grande responsabilidade. Recursos que lhe dão mais poder também lhe dão mais maneiras de se atirar no pé e, obviamente, devem ser usados de forma criteriosa.

Por exemplo, faz todo o sentido sobrecarregar o operador + ou * para class Matrix ou class Complex . Todos saberão instantaneamente o que isso significa. Por outro lado, para mim o fato de + significar concatenação de strings não é de todo óbvio, mesmo que Java faça isso como parte da linguagem, e STL faz por std::string usando a sobrecarga de operadores.

Outro bom exemplo de quando a sobrecarga do operador torna o código mais claro são os ponteiros inteligentes em C ++. Você deseja que os ponteiros inteligentes se comportem como ponteiros regulares o máximo possível, por isso faz todo o sentido sobrecarregar os operadores * e -> unários.

Em essência, a sobrecarga de operadores nada mais é do que apenas outra maneira de nomear uma função. E há uma regra para nomear funções: o nome deve ser descritivo, tornando imediatamente óbvio o que a função faz. A mesma regra exata se aplica à sobrecarga do operador.

    
por 10.12.2010 / 16:16
fonte
9

Em Haskell "+", "-", "*", "/" etc são apenas funções (infix).

Você deve nomear uma função infix "plus" como em "4 mais 2"? Por que não, se além disso é o que sua função faz. Você deve nomear sua função "mais" "+"? Porque não.

Eu acho que o problema com os chamados "operadores" é que eles se assemelham principalmente a operações matemáticas e não há muitas maneiras de interpretá-los e, portanto, há grandes expectativas sobre o que tal método / função / operador faz.

EDIT: deixou meu ponto mais claro

    
por 10.12.2010 / 13:18
fonte
7

Com base nas outras respostas que vi, só posso concluir que a objeção real à sobrecarga do operador é o desejo de código imediatamente óbvio.

Isso é trágico por dois motivos:

  1. Levando em conta sua conclusão lógica, o princípio de que o código deve ser imediatamente óbvio teria todos nós ainda codificando em COBOL.
  2. Você não aprende com código que é imediatamente óbvio. Você aprende com código que faz sentido quando você leva algum tempo para pensar sobre como funciona.
por 10.12.2010 / 17:06
fonte
5

Concordo um pouco.

Se você escrever multiply(j,5) , j poderia ser um tipo escalar ou de matriz, tornando multiply() mais ou menos complexo, dependendo de qual j é. No entanto, se você abandonar completamente a idéia de sobrecarregar, a função teria que ser nomeada multiply_scalar() ou multiply_matrix() , o que tornaria óbvio o que está acontecendo embaixo.

Existe um código onde muitos de nós preferiríamos de uma forma e há código onde a maioria de nós preferiria o contrário. A maior parte do código, no entanto, cai no meio termo entre esses dois extremos. O que você prefere lá depende do seu histórico e preferências pessoais.

    
por 10.12.2010 / 13:15
fonte
4

Eu vejo dois problemas com a sobrecarga do operador.

  1. Sobrecarga altera a semântica do operador, mesmo que isso não seja pretendido pelo programador. Por exemplo, quando você sobrecarrega && , || ou , , perde os pontos de sequência implícitos pelas variantes incorporadas desses operadores (assim como o comportamento de curto-circuito dos operadores lógicos). Por esse motivo, é melhor não sobrecarregar esses operadores, mesmo que a linguagem permita.
  2. Algumas pessoas vêem a sobrecarga do operador como um recurso interessante, elas começam a usá-lo em todos os lugares, mesmo que não seja a solução apropriada. Isso faz com que outras pessoas reajam exageradamente na outra direção e avisem contra o uso de sobrecarga do operador. Eu não concordo com nenhum dos dois grupos, mas tomo a posição intermediária: a sobrecarga do operador deve ser usada com parcimônia e somente quando
    • o operador sobrecarregado tem o significado natural para os especialistas em domínio e para os especialistas em software. Se esses dois grupos não concordarem com o significado natural do operador, não o sobrecarregue.
    • para o (s) tipo (s) envolvido (s), não há significado natural para o operador e o contexto imediato (preferencialmente a mesma expressão, mas não mais que algumas linhas) sempre deixa claro qual é o significado do operador. Um exemplo dessa categoria seria operator<< para fluxos.
por 10.12.2010 / 16:02
fonte
3

Com base em minha experiência pessoal, a maneira Java de permitir vários métodos, mas não sobrecarregar o operador, significa que sempre que você vir um operador, saberá exatamente o que ele faz.

Você não precisa ver se * invoca código estranho, mas sabe que ele é um multiply, e ele se comporta exatamente como está definido pela Especificação da Linguagem Java. Isso significa que você pode se concentrar no comportamento real em vez de descobrir todos os itens definidos pelo programador.

Em outras palavras, proibir a sobrecarga do operador é um benefício para o leitor , não para o escritor e, portanto, torna os programas mais fáceis de manter!

    
por 10.12.2010 / 13:20
fonte
3

Uma diferença entre sobrecarregar a * b e chamar multiply(a,b) é que o último pode ser facilmente usado. Se a função multiply não estiver sobrecarregada para tipos diferentes, você poderá descobrir exatamente o que a função fará, sem precisar rastrear os tipos de a e b .

Linus Torvalds tem um argumento interessante sobre sobrecarga do operador. Em algo como o desenvolvimento de kernel do Linux, onde a maioria das mudanças são enviadas via correções por e-mail, é importante que os mantenedores possam entender o que um patch fará com apenas algumas linhas de contexto ao redor de cada mudança. Se as funções e os operadores não estiverem sobrecarregados, o patch poderá ser lido com mais facilidade de um modo independente do contexto, já que você não precisa percorrer o arquivo alterado para descobrir quais são todos os tipos e verificar se há operadores sobrecarregados.

    
por 10.12.2010 / 14:19
fonte
2

Eu suspeito que tenha algo a ver com a quebra de expectativas. Se você está acostumado com o C ++, está acostumado a que o comportamento do operador não seja determinado inteiramente pela linguagem e não se surpreenderá se um operador fizer algo estranho. Se você está acostumado a linguagens que não têm esse recurso e, em seguida, ver o código C ++, você traz as expectativas desses outros idiomas e pode ser surpreendido quando você descobre que um operador sobrecarregado faz algo funky.

Pessoalmente, acho que há uma diferença. Quando você pode alterar o comportamento da sintaxe interna da linguagem, ela se torna mais opaca. Idiomas que não permitem a meta-programação são sintaticamente menos poderosos, mas conceitualmente mais simples de entender.

    
por 10.12.2010 / 12:56
fonte
2

Eu acho que sobrecarregar os operadores matemáticos não é o problema real com a sobrecarga do operador em C ++. Eu acho que sobrecarregar os operadores que não devem depender do contexto da expressão (ou seja, tipo) é "mal". Por exemplo. sobrecarregando , [ ] ( ) -> ->* new delete ou até mesmo o unário * . Você tem um certo conjunto de expectativas daqueles operadores que nunca devem mudar.

    
por 10.12.2010 / 13:46
fonte
2

Eu entendo perfeitamente que você não gosta do argumento de Joel sobre se esconder. Nem eu. Na verdade, é muito melhor usar '+' para coisas como tipos numéricos internos ou para os seus próprios, como, por exemplo, matriz. Eu admito que isso é puro e elegante para poder multiplicar duas matrizes com o '*' em vez de '.multiply ()'. E depois de tudo nós temos o mesmo tipo de abstração em ambos os casos.

O que dói aqui é a legibilidade do seu código. Em casos da vida real, não no exemplo acadêmico de multiplicação de matrizes. Especialmente se o seu idioma permite definir operadores que inicialmente não estão presentes no núcleo do idioma, por exemplo =:= . Muitas questões extras surgem neste momento. O que é esse maldito operador? Quero dizer, qual é a precedência disso? Qual é a associatividade? Em que ordem o a =:= b =:= c é realmente executado?

Isso já é um argumento contra sobrecarga do operador. Ainda não está convencido? Verificar as regras de precedência não levou mais de 10 segundos? Ok, vamos mais longe.

Se você começar a usar um idioma que permita a sobrecarga do operador, por exemplo aquele popular cujo nome começa com 'S', você aprenderá rapidamente que os designers da biblioteca adoram substituir os operadores. É claro que eles são bem instruídos, seguem as melhores práticas (sem cinismo aqui) e todas as suas APIs fazem todo o sentido quando as olhamos separadamente.

Agora imagine que você precisa usar algumas APIs que fazem uso pesado de sobrecarga de operadores em um único código. Ou melhor ainda - você tem que ler algum código legado como esse. É quando a sobrecarga do operador realmente é uma droga. Basicamente, se houver muitos operadores sobrecarregados em um lugar, eles logo começarão a se misturar com os outros caracteres não alfanuméricos no seu código de programa. Eles se misturam com caracteres não-alfanuméricos que não são realmente operadores, mas sim alguns elementos gramaticais de linguagem mais fundamentais que definem coisas como blocos e escopos, formatam instruções de controle de fluxo ou denotam algumas meta-coisas. Você precisará colocar os óculos e mover os olhos 10 cm para mais perto do monitor LCD para entender essa bagunça visual.

    
por 16.04.2015 / 19:29
fonte
1

Em geral, evito usar a sobrecarga de operadores de maneiras não intuitivas. Ou seja, se eu tiver uma classe numérica, a sobrecarga * é aceitável (e incentivada). No entanto, se eu tiver uma classe Employee, o que sobrecarregar * fará? Em outras palavras, sobrecarregue os operadores de maneiras intuitivas que facilitam a leitura e a compreensão.

Aceitável / incentivado:

class Complex
{
public:
    double r;
    double i;

    Complex operator*(const Compex& rhs)
    {
        Complex result;
        result.r = (r * rhs.r) - (i * rhs.i);
        result.i = (r * rhs.i) + (i * rhs.r);
        return result;
    }
};

Não aceitável:

class Employee
{
public:
    std::string name;
    std::string address;
    std::string phone_number;

    Employee operator* (const Employee& e)
    {
        // what the hell do I do here??
    }
};
    
por 10.12.2010 / 21:26
fonte
1

Além do que já foi dito aqui, há mais um argumento contra a sobrecarga do operador. De fato, se você escreve + , isso é meio óbvio que você quer dizer adição de algo a algo. Mas isso nem sempre é o caso.

O próprio C ++ fornece um ótimo exemplo de tal caso. Como é que stream << 1 deveria ser lido? fluxo deslocado para a esquerda por 1? Não é óbvio, a menos que você saiba explicitamente que < < em C ++ também grava no fluxo. No entanto, se essa operação fosse implementada como um método, nenhum desenvolvedor sensato gravaria o.leftShift(1) , seria algo como o.write(1) .

O ponto principal é que, ao indisponibilizar a sobrecarga do operador, a linguagem faz os programadores pensarem sobre os nomes das operações. Mesmo que o nome escolhido não seja perfeito, ainda é mais difícil interpretar mal um nome do que um sinal.

    
por 08.10.2011 / 14:04
fonte
1

Em comparação com os métodos escritos, os operadores são mais curtos, mas também não exigem parênteses. Parênteses são relativamente inconvenientes para digitar. E você deve equilibrá-los. No total, qualquer chamada de método requer três caracteres de ruído claro em comparação a um operador. Isso torna o uso de operadores muito, muito tentador.
Por que mais alguém iria querer isso: cout << "Hello world" ?

O problema com a sobrecarga é que a maioria dos programadores é incrivelmente preguiçosa e a maioria dos programadores não pode se dar ao luxo de ser.

O que leva os programadores C ++ ao abuso da sobrecarga do operador é a não sua presença, mas a ausência de uma maneira mais eficiente de realizar chamadas de método. E as pessoas não estão apenas com medo da sobrecarga do operador porque é possível, mas porque está feito.
Observe que, por exemplo, em Ruby e Scala, ninguém tem medo da sobrecarga do operador. Além do fato de que o uso de operadores não é realmente mais curto que os métodos, outra razão é que Ruby limita a sobrecarga do operador a um mínimo razoável, enquanto o Scala permite que você declare seus próprios operadores , tornando assim a evitação de colisões trivial.

    
por 08.10.2011 / 23:08
fonte
0

A razão pela qual o Sobrecarga do Operador é assustador, é porque há um grande número de programadores que nunca pensariam que * não significa simplesmente "multiplicar", enquanto um método como foo.multiply(bar) pelo menos instantaneamente aponta para aquele programador que alguém escreveu um método de multiplicação personalizado. Em que ponto eles se perguntam por que e vão investigar.

Eu tenho trabalhado com "bons programadores" que estavam em posições de alto nível que criariam métodos chamados "CompareValues" que levariam 2 argumentos, e aplicariam os valores de um para o outro e retornariam um booleano. Ou um método chamado "LoadTheValues" que iria para o banco de dados para 3 outros objetos, obter valores, fazer cálculos, modificar this e salvá-lo no banco de dados.

Se eu estou trabalhando em uma equipe com esses tipos de programadores, eu imediatamente sei investigar as coisas em que eles trabalharam. Se eles sobrecarregaram um operador, não tenho como saber que fizeram isso, exceto para assumir o que fizeram e procurar.

Em um mundo perfeito, ou uma equipe com programadores perfeitos, a sobrecarga do operador é provavelmente uma ferramenta fantástica. Eu ainda tenho que trabalhar em uma equipe de programadores perfeitos, então é por isso que é assustador.

    
por 08.10.2011 / 22:28
fonte