Estou aprendendo sobre a sobrecarga de operadores em C ++ e vejo isso ==
e !=
são simplesmente algumas funções especiais que podem ser personalizadas para tipos definidos pelo usuário. Minha preocupação é, porém, por que são necessárias duas definições separadas ? Eu pensei que se a == b
é verdade, então a != b
é automaticamente falsa e vice-versa, e não há outra possibilidade, porque, por definição, a != b
é !(a == b)
. E eu não conseguia imaginar nenhuma situação em que isso não fosse verdade. Mas talvez minha imaginação seja limitada ou eu ignore alguma coisa?
Eu sei que posso definir um em termos do outro, mas não é sobre isso que estou perguntando. Também não estou perguntando sobre a distinção entre comparar objetos por valor ou por identidade. Ou se dois objetos podem ser iguais e não iguais ao mesmo tempo (isso definitivamente não é uma opção! Essas coisas são mutuamente exclusivas). O que estou perguntando é o seguinte:
Existe alguma situação possível em que fazer perguntas sobre dois objetos serem iguais faz sentido, mas perguntar sobre eles não serem iguais não faz sentido? (da perspectiva do usuário ou da perspectiva do implementador)
Se não existe essa possibilidade, por que C ++ na Terra esses dois operadores são definidos como duas funções distintas?
fonte
'undefined' != expression
é sempre verdadeiro (ou falso ou indefinido), independentemente de a expressão poder ser avaliada. Nesse casoa!=b
, retornaria o resultado correto conforme a definição, mas!(a==b)
falharia seb
não puder ser avaliado. (Ou gaste muito tempo se a avaliaçãob
for cara).(NaN != NaN) == true
Respostas:
Você poderia não deseja que o idioma para reescrever automaticamente
a != b
como!(a == b)
quandoa == b
retorna algo diferente de umbool
. E há algumas razões pelas quais você pode fazer isso.Você pode ter objetos do construtor de expressões, nos quais
a == b
não tem e não se destina a realizar nenhuma comparação, mas simplesmente cria algum nó de expressão representandoa == b
.Você pode ter uma avaliação preguiçosa, onde
a == b
não pretende e não realiza nenhuma comparação diretamente, mas retorna algum tipolazy<bool>
que pode ser convertido embool
implicitamente ou explicitamente em algum momento posterior para realmente executar a comparação. Possivelmente combinado com os objetos do construtor de expressões para permitir otimização completa da expressão antes da avaliação.Você pode ter alguma
optional<T>
classe de modelo personalizada , onde são fornecidas variáveis opcionaist
eu
, você deseja permitirt == u
, mas faça com que ela retorneoptional<bool>
.Provavelmente há mais em que não pensei. E mesmo que nesses exemplos a operação
a == b
ea != b
as duas façam sentido, aindaa != b
não é a mesma coisa!(a == b)
, portanto são necessárias definições separadas.fonte
!=
em vez de duas passagens de computação==
em seguida!
. Especialmente no dia em que você não podia confiar no compilador para fundir os loops. Ou ainda hoje, se você não conseguir convencer o compilador, seus vetores não se sobrepõem.!
também pode construir algum nó de expressão e ainda estamos substituindoa != b
-o!(a == b)
por enquanto. O mesmo vale paralazy<bool>::operator!
, pode retornarlazy<bool>
.optional<bool>
é mais convincente, pois a veracidade lógica de, por exemplo,boost::optional
depende da existência de um valor, não do valor em si.Nan
es - por favor lembre-se dosNaN
;Porque você pode sobrecarregá-los e, sobrecarregando-os, pode dar a eles um significado totalmente diferente do original.
Tomemos, por exemplo, operador
<<
, originalmente o operador de mudança à esquerda bit a bit, agora geralmente sobrecarregado como um operador de inserção, como emstd::cout << something
; significado totalmente diferente do original.Portanto, se você aceitar que o significado de um operador muda quando você o sobrecarrega, não há motivo para impedir que o usuário dê um significado ao operador
==
que não seja exatamente a negação do operador!=
, embora isso possa ser confuso.fonte
==
e!=
existir como operadoras distintas. Por outro lado, eles provavelmente não existem como operadores distintos porque você pode sobrecarregá-los separadamente, mas devido a motivos herdados e de conveniência (brevidade do código).Você não precisa definir os dois.
Se eles são mutuamente exclusivos, você ainda pode ser conciso definindo apenas
==
e<
ao lado de std :: rel_opsDe preferência:
Frequentemente, associamos esses operadores à igualdade.
Embora seja assim que eles se comportam em tipos fundamentais, não há obrigação de que esse seja o comportamento deles em tipos de dados personalizados. Você nem precisa retornar um bool se não quiser.
Vi pessoas sobrecarregar os operadores de maneiras bizarras, apenas para descobrir que faz sentido para a aplicação específica de seu domínio. Mesmo que a interface pareça mostrar que eles são mutuamente exclusivos, o autor pode querer adicionar lógica interna específica.
Eu sei que você quer um exemplo específico,
então aqui está um da estrutura de teste Catch que eu pensei que era prático:
Esses operadores estão fazendo coisas diferentes, e não faria sentido definir um método como um! (Não) do outro. A razão disso é que o framework pode imprimir a comparação feita. Para fazer isso, ele precisa capturar o contexto de qual operador sobrecarregado foi usado.
fonte
std::rel_ops
? Muito obrigado por apontar isso.rel_ops
é horrível de qualquer maneira.Existem algumas convenções muito bem estabelecidas nas quais
(a == b)
eambos(a != b)
sãofalsos,não necessariamente opostos. Em particular, no SQL, qualquer comparação com NULL produz NULL, não é verdadeiro ou falso.Provavelmente, não é uma boa ideia criar novos exemplos disso, se possível, porque é tão pouco intuitivo, mas se você estiver tentando modelar uma convenção existente, é bom ter a opção de fazer com que seus operadores se comportem "corretamente" para isso. contexto.
fonte
NULL == something
retornar Desconhecido e tambémNULL != something
retornaria Desconhecido, e desejaria!Unknown
retornarUnknown
. E, nesse caso, a implementaçãooperator!=
como negação deoperator==
ainda está correta.operator==
ouoperator!=
, mas não o outro, ou 2) implementaroperator!=
de uma maneira diferente da negação deoperator==
. E implementar a lógica SQL para valores NULL não é um caso disso.Eu responderei apenas a segunda parte da sua pergunta, a saber:
Uma razão pela qual faz sentido permitir que o desenvolvedor sobrecarregue ambos é o desempenho. Você pode permitir otimizações implementando ambos
==
e!=
. Entãox != y
pode ser mais barato do que!(x == y)
é. Alguns compiladores podem otimizar para você, mas talvez não, especialmente se você tiver objetos complexos com muitas ramificações envolvidas.Mesmo em Haskell, onde os desenvolvedores levam leis e conceitos matemáticos muito a sério, ainda é permitido sobrecarregar ambos
==
e/=
, como você pode ver aqui ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v: -61--61- ):Isso provavelmente seria considerado micro-otimização, mas pode ser justificado em alguns casos.
fonte
pcmpeqb
instrução, mas nenhuma instrução de comparação compactada produzindo uma máscara! =. Portanto, se você não pode simplesmente reverter a lógica do que quer que use os resultados, precisará usar outra instrução para invertê-la. (Fato interessante: o conjunto de instruções XOP da AMD tem comparação compacta paraneq
. Pena que a Intel não adotou / estendeu o XOP; existem algumas instruções úteis nessa extensão ISA que está prestes a morrer.)PXOR
com todas as unidades para inverter o resultado da máscara de comparação) em um loop restrito pode ser importante.x == y
custar mais significativamente do quex != y
. Computar o último pode ser significativamente mais barato devido à previsão de desvios, etc.Essa é uma opinião. Talvez não. Mas os designers de linguagem, por não serem oniscientes, decidiram não restringir as pessoas que poderiam ter situações em que isso pudesse fazer sentido (pelo menos para elas).
fonte
Em resposta à edição;
Em geral , não, não faz sentido. Operadores relacionais e de igualdade geralmente vêm em conjuntos. Se existe a igualdade, também a desigualdade; menos que, então maior que e assim por diante com o
<=
etc. Uma abordagem semelhante também é aplicada aos operadores aritméticos, eles geralmente também vêm em conjuntos lógicos naturais.Isso é evidenciado no
std::rel_ops
espaço para nome. Se você implementar a igualdade e menos que operadores, o uso desse espaço para nome fornecerá os outros, implementados em termos de seus operadores implementados originais.Dito isto, existem condições ou situações em que um não significaria imediatamente o outro ou não poderia ser implementado em termos dos outros? Sim, existem , sem dúvida, poucos, mas eles estão lá; novamente, como evidenciado por
rel_ops
ser um espaço de nome próprio. Por esse motivo, permitir que eles sejam implementados de forma independente permite que você aproveite o idioma para obter a semântica necessária ou requerida de uma maneira ainda natural e intuitiva para o usuário ou cliente do código.A avaliação preguiçosa já mencionada é um excelente exemplo disso. Outro bom exemplo é dar a eles semânticas que não significam igualdade ou desigualdade. Um exemplo semelhante a este são os operadores de troca de bits
<<
e>>
sendo usados para inserção e extração de fluxo. Embora possa ser desaprovado nos círculos gerais, em algumas áreas específicas do domínio, pode fazer sentido.fonte
Se os operadores
==
e!=
realmente não implicam igualdade, da mesma forma que os operadores<<
e>>
stream não implicam mudança de bits. Se você tratar os símbolos como se eles significassem algum outro conceito, eles não precisam ser mutuamente exclusivos.Em termos de igualdade, faria sentido se o seu caso de uso justificasse o tratamento de objetos como não comparáveis, para que toda comparação retorne falso (ou um tipo de resultado não comparável, se seus operadores retornarem não booleanos). Não consigo pensar em uma situação específica em que isso seria justificado, mas pude ver que isso é razoável o suficiente.
fonte
Com grande poder, é ótimo responsável, ou pelo menos realmente bons guias de estilo.
==
e!=
pode ser sobrecarregado para fazer o que você quiser. É uma bênção e uma maldição. Não há garantia de que isso!=
signifique!(a==b)
.fonte
Não posso justificar essa sobrecarga de operador, mas no exemplo acima é impossível definir
operator!=
como o "oposto" deoperator==
.fonte
!=
isso não significaria o oposto de==
.==
?No final, o que você está verificando com esses operadores é que a expressão
a == b
oua != b
está retornando um valor booleano (true
oufalse
). Essa expressão retorna um valor booleano após a comparação, em vez de ser mutuamente exclusiva.fonte
Uma coisa a considerar é que pode haver a possibilidade de implementar um desses operadores com mais eficiência do que apenas usar a negação do outro.
(Meu exemplo aqui foi lixo, mas o ponto ainda permanece, pense nos filtros de bloom, por exemplo: Eles permitem testes rápidos se algo não estiver em um conjunto, mas testar se estiver dentro pode levar muito mais tempo.)
E é sua responsabilidade como programador fazer isso. Provavelmente é uma boa coisa para se escrever um teste.
fonte
!((a == rhs.a) && (b == rhs.b))
não permite curto-circuito? se!(a == rhs.a)
, então(b == rhs.b)
não será avaliado.==
, ele parará de comparar assim que os primeiros elementos correspondentes não forem iguais. Mas, no caso de!=
, se ele fosse implementado em termos de==
, seria necessário comparar todos os elementos correspondentes primeiro (quando todos forem iguais) para poder dizer que eles não são diferentes: P Mas quando implementado como em No exemplo acima, ele será interrompido assim que encontrar o primeiro par não igual. Grande exemplo, de fato.!((a == b) && (c == d))
e(a != b) || (c != d)
são equivalentes em termos de eficiência em curto-circuito.Ao personalizar o comportamento dos operadores, você pode fazê-los fazer o que quiser.
Você pode personalizar as coisas. Por exemplo, você pode personalizar uma classe. Objetos desta classe podem ser comparados apenas verificando uma propriedade específica. Sabendo que esse é o caso, você pode escrever um código específico que apenas verifique as coisas mínimas, em vez de verificar cada bit de cada propriedade no objeto inteiro.
Imagine um caso em que você possa descobrir que algo é diferente com a mesma rapidez, se não mais rápido, do que descobrir que algo é o mesmo. É verdade que, depois de descobrir se algo é igual ou diferente, você pode saber o contrário simplesmente mudando um pouco. No entanto, inverter esse bit é uma operação extra. Em alguns casos, quando o código é reexecutado muito, salvar uma operação (multiplicada por várias vezes) pode ter um aumento geral na velocidade. (Por exemplo, se você salvar uma operação por pixel de uma tela de megapixel, poderá salvar um milhão de operações. Multiplicado por 60 telas por segundo e salvar ainda mais operações.)
A resposta do hvd fornece alguns exemplos adicionais.
fonte
Sim, porque um significa "equivalente" e outro significa "não equivalente" e esses termos são mutuamente exclusivos. Qualquer outro significado para esses operadores é confuso e deve ser evitado por todos os meios.
fonte
a != b
não é igual a!(a == b)
esse motivo em C?Talvez uma regra incomparável, onde
a != b
era falsa ea == b
era falsa como um pouco apátrida.fonte
operator==()
eoperator!=()
não são necessariamentebool
, eles podem ser um enum que incluem apátrida se você queria isso e ainda os operadores ainda pode ser definido de modo(a != b) == !(a==b)
detém ..