Estoy aprendiendo sobre la sobrecarga de operadores en C ++, y veo eso ==
y !=
son simplemente algunas funciones especiales que se pueden personalizar para los tipos definidos por el usuario. Sin embargo, mi preocupación es, ¿por qué se necesitan dos definiciones separadas ? Pensé que si a == b
es verdad, entonces a != b
es automáticamente falso, y viceversa, y no hay otra posibilidad, porque, por definición, lo a != b
es !(a == b)
. Y no podía imaginar ninguna situación en la que esto no fuera cierto. ¿Pero quizás mi imaginación es limitada o soy ignorante de algo?
Sé que puedo definir uno en términos del otro, pero esto no es lo que estoy preguntando. Tampoco estoy preguntando sobre la distinción entre comparar objetos por valor o por identidad. O si dos objetos podrían ser iguales y no iguales al mismo tiempo (¡esto definitivamente no es una opción! Estas cosas son mutuamente excluyentes). Lo que pregunto es esto:
¿Hay alguna situación posible en la que hacer preguntas acerca de que dos objetos sean iguales no tiene sentido, pero preguntar si no son iguales no tiene sentido? (ya sea desde la perspectiva del usuario o desde la perspectiva del implementador)
Si no existe tal posibilidad, ¿por qué en la Tierra C ++ tiene estos dos operadores definidos como dos funciones distintas?
fuente
'undefined' != expression
siempre es verdadero (o falso o indefinido), independientemente de si la expresión puede evaluarse. En este casoa!=b
, devolvería el resultado correcto según la definición, pero!(a==b)
fallaría sib
no se puede evaluar. (O tome mucho tiempo si la evaluaciónb
es costosa).(NaN != NaN) == true
Respuestas:
Usted no desea que el idioma de reescribir automáticamente
a != b
como!(a == b)
cuando sea == b
vuelve algo más que unabool
. Y hay algunas razones por las que podrías hacer que lo haga.Es posible que tenga objetos de creación de expresiones, donde
a == b
no se pretende y no se pretende realizar ninguna comparación, sino que simplemente crea algún nodo de expresión que representaa == b
.Es posible que tenga una evaluación diferida, donde
a == b
no se pretende y no se pretende realizar ninguna comparación directamente, sino que se devuelve algún tipo delazy<bool>
eso que se puede convertirbool
implícita o explícitamente en algún momento posterior para realizar la comparación. Posiblemente combinado con los objetos del generador de expresiones para permitir la optimización completa de la expresión antes de la evaluación.Puede tener alguna
optional<T>
clase de plantilla personalizada , donde se le dan variables opcionalest
yu
desea permitirt == u
, pero hacer que regreseoptional<bool>
.Probablemente hay más en lo que no pensé. Y aunque en estos ejemplos la operación
a == b
y lasa != b
dos tienen sentido, todavíaa != b
no es lo mismo!(a == b)
, por lo que se necesitan definiciones separadas.fuente
!=
en lugar de dos pases de computación==
a continuación!
. Especialmente en aquellos días en los que no podías confiar en el compilador para fusionar los bucles. O incluso hoy, si no logra convencer al compilador, sus vectores no se superponen.!
también puede construir algún nodo de expresión y eso que todavía estamos reemplazandoa != b
con!(a == b)
, en lo que va. Lo mismo ocurrelazy<bool>::operator!
, puede volverlazy<bool>
.optional<bool>
es más convincente, ya que la veracidad lógica de, por ejemplo,boost::optional
depende de si existe un valor, no del valor en sí mismo.Nan
s - por favor recuerde elNaN
s;Porque puedes sobrecargarlos, y al sobrecargarlos puedes darles un significado totalmente diferente al original.
Tomemos, por ejemplo, operador
<<
, originalmente el operador de desplazamiento a la izquierda bit a bit, ahora comúnmente sobrecargado como operador de inserción, como enstd::cout << something
; significado totalmente diferente del original.Entonces, si acepta que el significado de un operador cambia cuando lo sobrecarga, entonces no hay razón para evitar que el usuario le dé un significado al operador
==
que no sea exactamente la negación del operador!=
, aunque esto puede ser confuso.fuente
==
y!=
existir como operadores distintos. Por otro lado, probablemente no existan como operadores distintos porque puede sobrecargarlos por separado, pero debido a razones de legado y conveniencia (brevedad del código).No tienes que definir ambos.
Si son mutuamente excluyentes, aún puede ser conciso definiendo
==
y<
junto con std :: rel_opsDe la preferencia:
A menudo asociamos estos operadores a la igualdad.
Aunque así es como se comportan en los tipos fundamentales, no hay obligación de que este sea su comportamiento en los tipos de datos personalizados. Ni siquiera tiene que devolver un bool si no lo desea.
He visto personas sobrecargar a los operadores de maneras extrañas, solo para descubrir que tiene sentido para su aplicación específica de dominio. Incluso si la interfaz parece mostrar que son mutuamente excluyentes, el autor puede querer agregar lógica interna específica.
Sé que quieres un ejemplo específico,
así que aquí hay uno del marco de prueba de Catch que pensé que era práctico:
Estos operadores están haciendo cosas diferentes, y no tendría sentido definir un método como un! (No) del otro. La razón por la que se hace esto es para que el marco pueda imprimir la comparación realizada. Para hacer eso, necesita capturar el contexto de qué operador sobrecargado se utilizó.
fuente
std::rel_ops
? Muchas gracias por señalarlo.rel_ops
Es horrible de todos modos.Hay algunas convenciones muy bien establecidas en la que
(a == b)
y(a != b)
sonambas falsasno necesariamente opuestos. En particular, en SQL, cualquier comparación con NULL produce NULL, no verdadero o falso.Probablemente no sea una buena idea crear nuevos ejemplos de esto si es posible, porque es muy poco intuitivo, pero si está intentando modelar una convención existente, es bueno tener la opción de hacer que sus operadores se comporten "correctamente" para eso contexto.
fuente
NULL == something
devolver Desconocido, y también desearíaNULL != something
devolver Desconocido y desearía!Unknown
regresarUnknown
? Y en ese caso, la implementaciónoperator!=
como la negación deoperator==
sigue siendo correcta.operator==
ooperator!=
, pero no el otro, o 2) implementaroperator!=
de una manera diferente a la negación deoperator==
. E implementar la lógica SQL para valores NULL no es un caso de eso.Solo responderé la segunda parte de su pregunta, a saber:
Una razón por la que tiene sentido permitir que el desarrollador sobrecargue ambos es el rendimiento. Puede permitir optimizaciones implementando ambos
==
y!=
. Entoncesx != y
podría ser más barato de lo que!(x == y)
es. Algunos compiladores pueden optimizarlo para usted, pero tal vez no, especialmente si tiene objetos complejos con muchas ramificaciones involucradas.Incluso en Haskell, donde los desarrolladores se toman muy en serio las leyes y los conceptos matemáticos, uno puede sobrecargar ambos
==
y/=
, como puede ver aquí ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v: -61--61- ):Esto probablemente se consideraría micro-optimización, pero podría estar justificado en algunos casos.
fuente
pcmpeqb
instrucción, pero ninguna instrucción de comparación de paquetes produce una máscara! =. Entonces, si no puede revertir la lógica de lo que sea que use los resultados, debe usar otra instrucción para invertirlo. (Dato curioso: el conjunto de instrucciones XOP de AMD tiene una comparación compactaneq
. Lástima que Intel no adoptó / extendió XOP; hay algunas instrucciones útiles en esa extensión ISA que pronto estará muerta).PXOR
con todos para invertir el resultado de la máscara de comparación) en un ciclo cerrado puede ser importante.x == y
cuesta más significativamente quex != y
. Cálculo de la última podría ser significativamente más barato debido a la predicción de ramificación, etc.Esa es una opinion. Quizás no. Pero los diseñadores del lenguaje, al no ser omniscientes, decidieron no restringir a las personas que podrían presentar situaciones en las que podría tener sentido (al menos para ellos).
fuente
En respuesta a la edición;
En general , no, no tiene sentido. La igualdad y los operadores relacionales generalmente vienen en conjuntos. Si existe la igualdad, entonces también la desigualdad; menor que, luego mayor que y así sucesivamente con el
<=
etc. También se aplica un enfoque similar a los operadores aritméticos, que generalmente también vienen en conjuntos lógicos naturales.Esto se evidencia en el
std::rel_ops
espacio de nombres. Si implementa los operadores de igualdad y menor que, el uso de ese espacio de nombres le proporciona los otros, implementados en términos de sus operadores implementados originales.Dicho todo esto, ¿hay condiciones o situaciones en las que una no significaría inmediatamente la otra o no podría implementarse en términos de las otras? Sí, hay pocos, pero están allí; de nuevo, como se evidencia en
rel_ops
ser un espacio de nombres propio. Por esa razón, permitir que se implementen de forma independiente le permite aprovechar el lenguaje para obtener la semántica que necesita o necesita de una manera que sigue siendo natural e intuitiva para el usuario o cliente del código.La evaluación perezosa ya mencionada es un excelente ejemplo de esto. Otro buen ejemplo es darles semánticas que no significan igualdad o desigualdad en absoluto. Un ejemplo similar a esto son los operadores de desplazamiento de bits
<<
y>>
se utilizan para la inserción y extracción de flujos. Aunque puede estar mal visto en círculos generales, en algunas áreas específicas del dominio puede tener sentido.fuente
Si los operadores
==
y!=
no implican realmente igualdad, de la misma manera que los operadores<<
y>>
stream no implican desplazamiento de bits. Si trata los símbolos como si significaran algún otro concepto, no tienen que ser mutuamente excluyentes.En términos de igualdad, podría tener sentido si su caso de uso garantiza tratar los objetos como no comparables, de modo que cada comparación devuelva falso (o un tipo de resultado no comparable, si sus operadores devuelven no bool). No puedo pensar en una situación específica en la que esto esté justificado, pero podría ver que es lo suficientemente razonable.
fuente
Con un gran poder viene de manera responsable, o al menos muy buenas guías de estilo.
==
y!=
se puede sobrecargar para hacer lo que quieras. Es a la vez una bendición y una maldición. No hay garantía que!=
signifique!(a==b)
.fuente
No puedo justificar la sobrecarga de este operador, pero en el ejemplo anterior es imposible definirlo
operator!=
como "opuesto" deoperator==
.fuente
!=
no significaría lo contrario de==
.==
?Al final, lo que está comprobando con esos operadores es que la expresión
a == b
oa != b
está devolviendo un valor booleano (true
ofalse
). Estas expresiones devuelven un valor booleano después de la comparación en lugar de ser mutuamente excluyentes.fuente
Una cosa a tener en cuenta es que puede existir la posibilidad de implementar uno de estos operadores de manera más eficiente que simplemente usar la negación del otro.
(Mi ejemplo aquí fue basura, pero el punto sigue en pie, piense en los filtros de floración, por ejemplo: permiten una prueba rápida si algo no está en un conjunto, pero probar si está dentro puede llevar mucho más tiempo).
Y es su responsabilidad como programador hacer eso. Probablemente sea algo bueno para escribir un examen.
fuente
!((a == rhs.a) && (b == rhs.b))
no permite cortocircuitos? si!(a == rhs.a)
, entonces(b == rhs.b)
no será evaluado.==
que, deje de comparar tan pronto como los primeros elementos correspondientes no sean iguales. Pero en caso de que!=
, si se implementara en términos de==
, tendría que comparar primero todos los elementos correspondientes (cuando son todos iguales) para poder decir que no son no iguales: P Pero cuando se implementa como en En el ejemplo anterior, dejará de compararse tan pronto como encuentre el primer par no igual. Gran ejemplo de hecho.!((a == b) && (c == d))
y(a != b) || (c != d)
son equivalentes en términos de eficiencia de cortocircuito.Al personalizar el comportamiento de los operadores, puede hacer que hagan lo que quiera.
Es posible que desee personalizar las cosas. Por ejemplo, es posible que desee personalizar una clase. Los objetos de esta clase se pueden comparar simplemente marcando una propiedad específica. Sabiendo que este es el caso, puede escribir un código específico que solo verifique las cosas mínimas, en lugar de verificar cada bit de cada propiedad en todo el objeto.
Imagine un caso en el que puede descubrir que algo es diferente tan rápido, si no más rápido, que puede descubrir que algo es igual. Por supuesto, una vez que descubres si algo es igual o diferente, entonces puedes saber lo contrario simplemente volteando un poco. Sin embargo, cambiar ese bit es una operación adicional. En algunos casos, cuando el código se vuelve a ejecutar mucho, guardar una operación (multiplicado por muchas veces) puede tener un aumento general de velocidad. (Por ejemplo, si guarda una operación por píxel de una pantalla de megapíxeles, entonces acaba de guardar un millón de operaciones. Multiplicado por 60 pantallas por segundo, y guarda aún más operaciones).
La respuesta de hvd proporciona algunos ejemplos adicionales.
fuente
Sí, porque uno significa "equivalente" y otro significa "no equivalente" y estos términos son mutuamente excluyentes. Cualquier otro significado para estos operadores es confuso y debe evitarse por todos los medios.
fuente
a != b
no sea igual!(a == b)
por esta razón en C?Tal vez una regla incomparable, donde
a != b
era falso ya == b
era falso como un bit sin estado.fuente
operator==()
yoperator!=()
no son necesariamentebool
, podrían ser una enumeración que incluya apátridas si así lo desea y, sin embargo, los operadores aún podrían definirse, así que se(a != b) == !(a==b)
mantiene ..