O que faz o operador da Scala sobrecarregar "bom", mas o C ++ é "ruim"?


155

A sobrecarga de operadores em C ++ é considerada por muitos como A Bad Thing (tm) e um erro que não deve ser repetido em idiomas mais novos. Certamente, esse foi um recurso descartado especificamente ao projetar Java.

Agora que comecei a ler o Scala, acho que ele tem o que se parece muito com sobrecarga de operador (embora tecnicamente não tenha sobrecarga de operador porque não possui operadores, apenas funções). No entanto, não parece ser qualitativamente diferente da sobrecarga de operadores em C ++, onde, pelo que me lembro, os operadores são definidos como funções especiais.

Então, minha pergunta é o que torna a idéia de definir "+" no Scala uma idéia melhor do que em C ++?


27
Nem C ++, nem Scala foi definido por consenso universal entre todos os programadores. Não acho que exista contradição entre o fato de algumas pessoas reclamarem do C ++ e o fato de algumas pessoas não reclamarem do Scala.
Steve Jessop

16
Não há nada ruim em sobrecarregar o operador em C ++.
Filhote de cachorro

5
Isso não é novidade, mas a maneira como defendo o C ++ quando a sobrecarga do operador e outros recursos "avançados" são questionados é simples: o C ++ nos dá todo o poder de usá-lo / abusá-lo como acharmos melhor. Eu sempre gostei de como somos considerados competentes e autônomos e não preciso de decisões como essa para nós.
Elliott #

Scala foi projetado como décadas depois do c ++. Acontece que a pessoa por trás disso é super sábia em termos de linguagens de programação. Nada de ruim por si só, se você se apegar ao c ++ ou ao Scala por mais 100 anos, fica claro que provavelmente os dois são ruins! Ser tendencioso é aparentemente de nossa natureza, mas podemos combatê-lo, basta olhar para a história da tecnologia, tudo se torna obsoleto.
Nader Ghanbari 16/10

Respostas:


242

C ++ herda operadores azuis verdadeiros de C. Com isso, quero dizer que o "+" em 6 + 4 é muito especial. Você não pode, por exemplo, obter um ponteiro para essa função +.

Scala, por outro lado, não tem operadores dessa maneira. Ele possui grande flexibilidade na definição de nomes de métodos, além de um pouco de precedência incorporada para símbolos que não são de palavras. Portanto, tecnicamente, o Scala não possui sobrecarga de operador.

Como você quiser chamá-lo, a sobrecarga do operador não é inerentemente ruim, mesmo em C ++. O problema é quando maus programadores abusam dele. Mas, francamente, sou da opinião de que tirar a capacidade dos programadores de abusar da sobrecarga do operador não prejudica a correção de tudo o que os programadores podem abusar. A verdadeira resposta é orientação. http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

Não obstante, existem diferenças entre a sobrecarga de operadores do C ++ e a nomeação flexível de métodos do Scala que, IMHO, tornam o Scala menos abusivo e mais abusivo.

No C ++, a única maneira de obter a notação de correção é usando operadores. Caso contrário, você deve usar object.message (argumento) ou ponteiro-> messsage (argumento) ou função (argumento1, argumento2). Portanto, se você deseja um determinado estilo DSLish no seu código, há pressão para usar operadores.

No Scala, você pode obter notação infix com qualquer mensagem enviada. "argumento de mensagem de objeto" está perfeitamente correto, o que significa que você não precisa usar símbolos que não sejam de palavras apenas para obter notação infix.

A sobrecarga do operador C ++ é limitada essencialmente aos operadores C. Combinado com a limitação de que apenas operadores podem ser usados ​​infixos que pressionam as pessoas a tentar mapear uma ampla gama de conceitos não relacionados em relativamente poucos símbolos como "+" e ">>"

O Scala permite uma grande variedade de símbolos válidos que não sejam palavras como nomes de métodos. Por exemplo, eu tenho um DSL Prolog-ish incorporado onde você pode escrever

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

Os símbolos: -,!,? E & são definidos como métodos comuns. Somente em C ++ e seria válido, portanto, uma tentativa de mapear esse DSL em C ++ exigiria alguns símbolos que já evocam conceitos muito diferentes.

Claro, isso também abre Scala para outro tipo de abuso. No Scala, você pode nomear um método como $! & ^%, Se desejar.

Para outras linguagens que, como Scala, são flexíveis no uso de nomes de funções e métodos que não sejam palavras, consulte Smalltalk, onde, como Scala, todo "operador" é apenas outro método e Haskell, que permite ao programador definir precedência e fixidez de nomes flexíveis. funções.


Última verificação, 3.operator + (5) funcionou. Estou realmente surpreso que & (3.operator +) não.
Joshua Joshua

você poderia, por exemplo, afirmar (feminino ("Jane")) em c ++. Isso não seria nada confuso - acene de volta ao post de James-Iry sobre o fato de o operador + não ser uma coisa ruim, mas os programadores estúpidos.
Pm10

1
@Joshua int main() {return (3).operator+(5);}resulta emerror: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01 /

É um monte de porcaria arrogante: "a sobrecarga do operador não é inerentemente ruim, mesmo em C ++. O problema é quando programadores ruins a abusam". Se algo é facilmente abusável com pouco benefício em usá-lo, o resultado geral é que o próximo responsável pela manutenção do seu código perderá produtividade ao decifrar as partes mais estranhas do seu código. Caso contrário: resposta muito informativa e bem escrita.
Jukka Dahlbom

@JukkaDahlbom A existência de ponteiros inteligentes aumenta por si só o benefício. E então você tem lambdas, tipos de números definidos pelo usuário, tipos de intervalo ...
Alexey Romanov

66

A sobrecarga do operador em C ++ é considerada por muitos como uma coisa ruim (tm)

Somente pelos ignorantes. É absolutamente necessário em uma linguagem como C ++, e é notável que outras linguagens que começaram a adotar uma visão "purista" a adicionaram quando seus designers descobriram o quão necessário é.


30
Na verdade, eu concordo com Neil. A sobrecarga do operador é essencial se você deseja apresentar variáveis ​​/ constantes / objetos / instâncias como entidades algébricas ... e fazer com que as pessoas entendam suas interações de maneira matemática - que deve ser como a programação funciona no IMHO.
Massa

16
+1, a sobrecarga do operador em C ++ é boa. Por exemplo, torna a matemática vetorial muito mais limpa. Como em muitos recursos do C ++, você deve exercer o poder com cuidado.
314 John Smith

7
@ Kristo Porque o C ++ usa valores que devem ser atribuídos e copiados. É necessário ter controle sobre isso, portanto, você deve poder especificar o operador de atribuição para um determinado tipo, no mínimo.

7
@ Kristo: porque uma intenção do C ++ é permitir que tipos definidos pelo usuário façam tudo o que os tipos internos fazem (embora sejam tratados de maneira diferente em alguns contextos, como conversões implícitas). Se você deseja implementar um número inteiro de 27 bits, é possível, e usá-lo será como usar int. Sem sobrecarga do operador, não seria possível usar UDTs com a mesma sintaxe dos tipos internos e, portanto, a linguagem resultante não seria "como C ++" nesse sentido.
Steve Jessop

8
"dessa maneira está a loucura" - ainda pior, está dessa maneira std :: vector <bool>!
Steve Jessop

42

A sobrecarga do operador nunca foi universalmente considerada uma má idéia em C ++ - apenas o abuso da sobrecarga do operador foi uma má idéia. Não é realmente necessário sobrecarregar o operador em um idioma, pois eles podem ser simulados com mais chamadas de função detalhadas. Evitar a sobrecarga do operador em Java tornou a implementação e a especificação do Java um pouco mais simples e forçou os programadores a não abusar dos operadores. Houve algum debate na comunidade Java sobre a introdução de sobrecarga do operador.

As vantagens e desvantagens da sobrecarga de operadores no Scala são as mesmas que no C ++ - você pode escrever um código mais natural se usar a sobrecarga de operador adequadamente - e um código oculto e oculto, se não o fizer.

FYI: Os operadores não são definidos como funções especiais em C ++, eles se comportam como qualquer outra função - embora existam algumas diferenças na pesquisa de nome, se precisam ser funções-membro e o fato de poderem ser chamadas de duas maneiras: 1 ) sintaxe do operador e 2) sintaxe do operador-função-ID.


"Não é realmente necessário sobrecarregar o operador em um idioma, pois eles podem ser simulados com mais chamadas de função detalhadas". Nem sequer precisamos de operadores sob essa lógica. Por que não usar apenas add(2, multiply(5, 3))?
Joe Z.

É mais um caso de combinar as notações usuais usadas. Considere matemáticos e físicos, eles podem entender e usar uma biblioteca C ++ que fornece sobrecargas dos operadores com muito mais facilidade. Eles preferem se concentrar na equação do que na linguagem de programação.
Phil Wright

19

Este artigo - " O legado positivo de C ++ e Java " - responde diretamente à sua pergunta.

"O C ++ possui alocação de pilha e alocação de pilha e você deve sobrecarregar seus operadores para lidar com todas as situações e não causar vazamentos de memória. De fato, é difícil. Java, no entanto, possui um único mecanismo de alocação de armazenamento e um coletor de lixo, o que torna a sobrecarga do operador trivial". ..

O Java erroneamente (de acordo com o autor) omitiu a sobrecarga do operador porque era complicado em C ++, mas esqueceu o porquê (ou não percebeu que não se aplicava ao Java).

Felizmente, linguagens de nível superior como o Scala oferecem opções para desenvolvedores, enquanto ainda estão em execução na mesma JVM.


14
Eckel é a única fonte que eu já vi para a idéia de que a sobrecarga do operador foi abandonada do Java por causa de complicações no C ++ e ele não diz qual é a sua fonte. Eu descontaria isso. Todas as outras fontes que afirmei que foram abandonadas devido a possíveis abusos. Veja gotw.ca/publications/c_family_interview.htm e newt.com/wohler/articles/james-gosling-ramblings-1.html . Basta procurar na página "sobrecarga do operador".
911 James Iry

9

Não há nada errado com a sobrecarga do operador. De fato, há algo errado em não haver sobrecarga de operador para tipos numéricos. (Dê uma olhada em algum código Java que usa BigInteger e BigDecimal.)

C ++ tem uma tradição de abusar do recurso, no entanto. Um exemplo frequentemente citado é que os operadores de deslocamento de bits estão sobrecarregados para fazer E / S.


Os operadores << e >> estão indicando visualmente a maneira de transferência, eles destinam-se à E / S, não é abuso, é da biblioteca padrão e coisa prática. Basta olhar para "cin >> alguma coisa", o que vai aonde? De cin, para alguma coisa, obviamente.
peenut 21/09/12

7
@peenut: Mas o uso original deles era de mudança de bits. A "biblioteca padrão" usa o operador de uma maneira que mexe completamente com a definição original.
Joe Z.

1
Tenho certeza de que li em algum lugar que Bjarne Stroustrup (criador do C ++) experimentou o uso em =vez de <<e >>nos primeiros dias do C ++, mas teve problemas porque não tinha a precedência correta do operador (ou seja, procura argumentos à esquerda ou à direita primeiro). Então, suas mãos estavam um pouco atadas sobre o que ele poderia usar.
Phil Wright

8

Em geral, não é uma coisa ruim.
Novos idiomas como C # também têm sobrecarga de operador.

É o abuso de sobrecarga do operador que é uma coisa ruim.

Mas também há problemas com a sobrecarga do operador, conforme definido em C ++. Como os operadores sobrecarregados são apenas açúcar sintático para chamadas de método, eles se comportam exatamente como o método. Por outro lado, os operadores internos normais não se comportam como métodos. Essas inconsistências podem causar problemas.

Em cima da minha cabeça operadores ||e &&.
As versões integradas destes são operadores de atalho. Isso não é verdade para versões sobrecarregadas e causou alguns problemas.

O fato de que + - * / todos retornam o mesmo tipo em que operam (após a promoção do operador)
As versões sobrecarregadas podem retornar qualquer coisa (é aqui que o abuso ocorre, se os operadores começarem a retornar algum tipo de árbitro que o usuário não esperava) as coisas descem).


8

Sobrecarga de operador não é algo que você realmente "precise" com muita frequência, mas ao usar Java, se você atingir um ponto em que realmente precisa, isso fará com que você queira arrancar as unhas apenas para ter uma desculpa para parar de digitar .

O código que você acabou de encontrar transborda por muito tempo? Sim, você precisará redigitar todo o lote para que funcione com o BigInteger. Não há nada mais frustrante que ter que reinventar a roda apenas para mudar o tipo de uma variável.


6

Guy Steele argumentou que a sobrecarga do operador também deveria estar em Java, em seu discurso "Crescendo uma linguagem" - há um vídeo e uma transcrição, e é realmente um discurso incrível. Você se perguntará sobre o que ele está falando nas primeiras páginas, mas se continuar lendo, verá o ponto e alcançará a iluminação. E o próprio fato de ele poder fazer esse discurso também é incrível.

Ao mesmo tempo, essa palestra inspirou muitas pesquisas fundamentais, provavelmente incluindo Scala - é um daqueles artigos que todos deveriam ler para trabalhar no campo.

De volta ao ponto, seus exemplos são principalmente sobre classes numéricas (como BigInteger e algumas coisas mais estranhas), mas isso não é essencial.

É verdade, porém, que o uso indevido da sobrecarga do operador pode levar a resultados terríveis e que mesmo os usos adequados podem complicar as coisas, se você tentar ler o código sem estudar um pouco as bibliotecas que ele usa. Mas isso é uma boa ideia? OTOH, essas bibliotecas não deveriam tentar incluir uma cábula de operador para seus operadores?


4

Acredito que CADA resposta perdeu isso. No C ++, você pode sobrecarregar os operadores o quanto quiser, mas não pode afetar a precedência com a qual eles são avaliados. Scala não tem esse problema, IIRC.

Por ser uma má idéia, além das questões de precedência, as pessoas criam significados realmente tolos para os operadores, e isso raramente ajuda na legibilidade. As bibliotecas Scala são especialmente ruins para isso, símbolos patetas que você deve memorizar a cada vez, com os mantenedores da biblioteca enfiando a cabeça na areia dizendo: 'você só precisa aprender uma vez'. Ótimo, agora eu preciso aprender a sintaxe enigmática de algum autor 'inteligente' * o número de bibliotecas que gostaria de usar. Não seria tão ruim se existisse uma convenção de SEMPRE fornecer uma versão alfabética dos operadores.


1
A Scala também tem precedência fixa no operador, não é?
21410 skaffman

Acredito que exista, mas é muito mais plano. Mais ao ponto, Scala tem menos período de operadores. +, -, * são métodos, não operadores, IIRC. É por isso que 2 + 3 * 2, não é 8, é 10.
Saem

7
Scala tem um sistema de precedência baseado no primeiro caractere do símbolo. scala> 2 + 3 * 2 res0: Int = 8
James Iry

3

A sobrecarga do operador não era uma invenção do C ++ - veio do Algol IIRC e até Gosling não afirma que é uma má ideia em geral.


Claro, mas foi na sua encarnação em C ++ que ganhou um ar geral de descrédito.
8138 skaffman

5
O que você quer dizer com "ar geral de descrédito"? A maioria das pessoas que conheço usa linguagens que suportam sobrecarga de operadores (C ++, C #) e nunca ouvi nenhuma reclamação.
Nemanja Trifunovic

Estou falando da minha experiência de longa data com o C ++ pré-ANSI, e certamente me lembro de uma aversão comum a eles. Talvez a situação tenha melhorado com o ANSI C ++ ou as pessoas tenham aprendido como não abusar.
8138 skaffman

1
Falando como alguém que usa C ++ desde os primeiros dias (meados dos anos 80), posso garantir que a introdução do padrão ISO não afetou os preconceitos das pessoas em relação à sobrecarga do operador.

3

A única coisa que se sabe de errado no C ++ é a falta de capacidade de sobrecarregar [] = como um operador separado. Isso pode ser difícil de implementar em um compilador C ++ pelo que provavelmente não é um motivo óbvio, mas vale a pena.


2

Como as outras respostas apontaram; sobrecarregar o operador não é necessariamente ruim. O que é ruim quando é usado de maneiras que tornam o código resultante não óbvio. Geralmente, ao usá-los, você precisa fazê-los fazer a coisa menos surpreendente (ter a divisão operator + do causaria problemas para o uso de uma classe racional) ou como Scott Meyers diz:

Os clientes já sabem como tipos como int se comportam; portanto, você deve se esforçar para que seus tipos se comportem da mesma maneira sempre que razoável ... Em caso de dúvida, faça como as ints . (Do item efetivo C ++ na 3ª edição 18)

Agora, algumas pessoas levaram a sobrecarga do operador ao extremo com coisas como boost :: spirit . Nesse nível, você não tem idéia de como é implementado, mas cria uma sintaxe interessante para obter o que deseja. Não tenho certeza se isso é bom ou ruim. Parece bom, mas eu não o usei.


Não estou discutindo a favor ou contra a sobrecarga do operador aqui, não estou procurando pessoas para justificá-las.
8139 skaffman

O Sprint não chega nem perto do pior exemplo que já encontrei - você deve ver o que a biblioteca de banco de dados do RogueWave faz!

Concordo que o Spirit usa mal os operadores, mas não consigo pensar em uma maneira melhor de fazê-lo.
Zifre

1
Eu não acho que o espírito esteja abusando dos operadores, mas está pressionando. Concordo que não há realmente outra maneira de fazer isso. Ele basicamente cria uma DSL dentro da sintaxe do C ++. Muito longe do que o C ++ foi projetado para fazer. Sim, existem exemplos muito piores :) Em geral, eu os uso quando apropriado. Principalmente apenas os operadores de streaming para facilitar a depuração \ log. E mesmo lá é apenas o açúcar que encaminha para um método implementado na classe.
Matt Price

1
É uma questão de gosto; mas as bibliotecas combinadoras do analisador, em linguagens funcionais, sobrecarregam os operadores de maneira muito semelhante ao Spirit, e ninguém argumenta contra isso. Existem várias razões técnicas pelas quais elas são melhores - o Google para "linguagens específicas de domínio incorporado" para encontrar vários artigos explicando isso de um ponto de vista geral e o Google para o "scala parser combinator" para exemplos práticos neste caso. É verdade que, em linguagens funcionais, a sintaxe resultante geralmente é mais agradável - por exemplo, você não precisa alterar o significado de >> para concatenar analisadores.
Blaisorblade

2

Eu nunca vi um artigo alegando que a sobrecarga de operador do C ++ seja ruim.

Os operadores definidos pelo usuário permitem um nível mais alto de expressividade e usabilidade para os usuários do idioma.


1

No entanto, não parece ser qualitativamente diferente da sobrecarga de operadores em C ++, onde, pelo que me lembro, os operadores são definidos como funções especiais.

AFAIK, Não há nada de especial nas funções do operador em comparação com as funções de membro "normais". É claro que você só tem um determinado conjunto de operadores que pode sobrecarregar, mas isso não os torna muito especiais.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.