singulares probabilidad independientes eventos ejemplos básicas c++ operator-overloading equality equality-operator

c++ - probabilidad - ¿Son== y!=Mutuamente dependientes?



formula de probabilidad ejemplos (15)

Estoy aprendiendo sobre la sobrecarga de operadores en C ++, y veo que == 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 verdadero, entonces a != b es automáticamente falso, y viceversa, y no hay otra posibilidad, porque, por definición, 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?


¿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)

Esa es una opinion. Tal vez no lo hace. Pero los diseñadores del lenguaje, al no ser omniscientes, decidieron no restringir a las personas que pudieran presentar situaciones en las que podría tener sentido (al menos para ellos).


Si no existe tal posibilidad, ¿por qué en la Tierra C ++ tiene estos dos operadores definidos como dos funciones distintas?

Debido a que puedes sobrecargarlos, y al sobrecargarlos puedes darles un significado totalmente diferente al original.

Tomemos, por ejemplo, el operador << , originalmente el operador de desplazamiento a la izquierda bit a bit, ahora comúnmente sobrecargado como operador de inserción, como en std::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 es exactamente la negación del operador != , Aunque esto puede ser confuso.


Sin embargo, mi preocupación es, ¿por qué se necesitan dos definiciones separadas?

No tienes que definir ambos.
Si son mutuamente excluyentes, aún puede ser conciso definiendo solo == y < junto con std::rel_ops

De preferencia:

#include <iostream> #include <utility> struct Foo { int n; }; bool operator==(const Foo& lhs, const Foo& rhs) { return lhs.n == rhs.n; } bool operator<(const Foo& lhs, const Foo& rhs) { return lhs.n < rhs.n; } int main() { Foo f1 = {1}; Foo f2 = {2}; using namespace std::rel_ops; //all work as you would expect std::cout << "not equal: : " << (f1 != f2) << ''/n''; std::cout << "greater: : " << (f1 > f2) << ''/n''; std::cout << "less equal: : " << (f1 <= f2) << ''/n''; std::cout << "greater equal: : " << (f1 >= f2) << ''/n''; }

¿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?

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 quiere.

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.

(ya sea desde la perspectiva del usuario o desde la perspectiva del implementador)

Sé que quieres un ejemplo específico,
así que aquí hay uno del marco de prueba de captura que pensé que era práctico:

template<typename RhsT> ResultBuilder& operator == ( RhsT const& rhs ) { return captureExpression<Internal::IsEqualTo>( rhs ); } template<typename RhsT> ResultBuilder& operator != ( RhsT const& rhs ) { return captureExpression<Internal::IsNotEqualTo>( rhs ); }

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ó.


[..] ¿por qué se necesitan dos definiciones separadas?

Una cosa a tener en cuenta es que podría 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).

[..] por definición, a != b es !(a == b) .

Y es su responsabilidad como programador hacer eso. Probablemente sea algo bueno para escribir un examen.


Al final, lo que está comprobando con esos operadores es que la expresión a == b a != b está devolviendo un valor booleano ( true o false ). Estas expresiones devuelven un valor booleano después de la comparación en lugar de ser mutuamente excluyentes.


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.


Con un gran poder viene de manera responsable, o al menos muy buenas guías de estilo.

== y != se pueden sobrecargar para hacer lo que quieras. Es a la vez una bendición y una maldición. No hay garantía de que != Significa !(a==b) .


En respuesta a la edición;

Es decir, si es posible que algún tipo tenga el operador == pero no el != , O viceversa, y cuándo tiene sentido hacerlo.

En general , no, no tiene sentido. La igualdad y los operadores relacionales generalmente vienen en conjuntos. Si existe la igualdad, también la desigualdad; menor que, luego mayor que y así sucesivamente con <= 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 nombres std::rel_ops . 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í; nuevamente, como se evidencia en que rel_ops es 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 ser mal visto en los círculos generales, en algunas áreas específicas del dominio puede tener sentido.


Hay algunas convenciones muy bien establecidas en las que (a == b) y (a != b) son falsas, no 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.


No querrá que el idioma reescriba automáticamente a != b as !(a == b) cuando a == b devuelve algo distinto de un bool . 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 lo hace y no pretende realizar ninguna comparación, sino que simplemente crea algún nodo de expresión que representa a == b .

Es posible que tenga una evaluación diferida, donde a == b no lo hace y no pretende realizar ninguna comparación directamente, sino que devuelve algún tipo de lazy<bool> que se puede convertir a bool implícita o explícitamente más adelante realiza 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.

Es posible que tenga alguna clase de plantilla optional<T> personalizada, en la que, dada las variables opcionales t y u , desea permitir t == u , pero hacer que devuelva optional<bool> .

Probablemente hay más en lo que no pensé. Y aunque en estos ejemplos la operación a == b y a != b tienen sentido, todavía a != b no es lo mismo que !(a == b) , por lo que se necesitan definiciones separadas.


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.


Si los operadores == y != realidad no implican igualdad, de la misma manera que los operadores de flujo << y >> 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.


Solo responderé la segunda parte de su pregunta, a saber:

Si no existe tal posibilidad, ¿por qué en la Tierra C ++ tiene estos dos operadores definidos como dos funciones distintas?

Una razón por la que tiene sentido permitir que el desarrollador sobrecargue ambos es el rendimiento. Puede permitir optimizaciones implementando ambos == y != . Entonces x != y podría ser más barato 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 tanto == como /= , como puede ver aquí ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude.html#v:-61--61- ):

$ ghci GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help λ> :i Eq class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool -- Defined in `GHC.Classes''

Esto probablemente se consideraría micro-optimización, pero podría estar justificado en algunos casos.


Tal vez una regla incomparable, donde a != b era falso y a == b era falso como un bit sin estado.

if( !(a == b || a != b) ){ // Stateless }


enum BoolPlus { kFalse = 0, kTrue = 1, kFileNotFound = -1 } BoolPlus operator==(File& other); BoolPlus operator!=(File& other);

No puedo justificar la sobrecarga de este operador, pero en el ejemplo anterior es imposible definir operator!= Como el "opuesto" de operator== .