tipos programas programa lenguaje ejemplos datos caracteristicas c++ operators

programas - ¿Por qué los compiladores de C++ no definen operador== y operador!=?



programa c++ (13)

Soy un gran fan de dejar que el compilador trabaje tanto por ti como sea posible. Al escribir una clase simple, el compilador puede darte lo siguiente de forma gratuita:

  • Un constructor por defecto (vacío)
  • Un constructor de copia
  • Un destructor
  • Un operador de asignación ( operator= )

Pero parece que no puede darle ningún operador de comparación, como el operator== o el operator!= . Por ejemplo:

class foo { public: std::string str_; int n_; }; foo f1; // Works foo f2(f1); // Works foo f3; f3 = f2; // Works if (f3 == f2) // Fails { } if (f3 != f2) // Fails { }

¿Hay alguna buena razón para esto? ¿Por qué sería un problema realizar una comparación miembro por miembro? Obviamente, si la clase asigna memoria, querrás tener cuidado, pero para una clase simple, ¿seguramente el compilador podría hacer esto por ti?


¿Hay alguna buena razón para esto? ¿Por qué sería un problema realizar una comparación miembro por miembro?

Puede que no sea un problema funcional, pero en términos de rendimiento, la comparación de miembro por miembro predeterminada puede ser más subóptima que la asignación / copia de miembro por miembro predeterminada. A diferencia del orden de asignación, el orden de comparación afecta el rendimiento porque el primer miembro desigual implica que el resto se puede omitir. Entonces, si hay algunos miembros que normalmente son iguales, querrás compararlos al final, y el compilador no sabe qué miembros tienen más probabilidades de ser iguales.

Considere este ejemplo, donde verboseDescription es una cadena larga seleccionada de un conjunto relativamente pequeño de posibles descripciones climáticas.

class LocalWeatherRecord { std::string verboseDescription; std::tm date; bool operator==(const LocalWeatherRecord& other){ return date==other.date && verboseDescription==other.verboseDescription; // The above makes a lot more sense than // return verboseDescription==other.verboseDescription // && date==other.date; // because some verboseDescriptions are liable to be same/similar } }

(Por supuesto, el compilador tendría derecho a ignorar el orden de las comparaciones si reconoce que no tienen efectos secundarios, pero es de suponer que todavía tomaría su cola del código fuente donde no tiene mejor información propia).


ACTUALIZACIÓN 2: Desafortunadamente, esta propuesta no llegó a C ++ 17 , por lo que nada está cambiando en el lenguaje con respecto a esto por ahora.

ACTUALIZACIÓN: La versión actual de la propuesta, que tiene muchas posibilidades de ser votada en C ++ 17, está open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0221r0.html .

Hay una propuesta reciente (N4126) sobre operadores de comparación con incumplimiento explícito, que ha tenido comentarios muy positivos del comité estándar, así que espero que la veamos de alguna forma en C ++ 17.

En resumen, la sintaxis propuesta es:

struct Thing { int a, b, c; std::string d; }; bool operator==(const Thing &, const Thing &)= default; bool operator!=(const Thing &, const Thing &)= default;

O en forma de friend para clases con campos privados:

class Thing { int a, b; friend bool operator<(Thing, Thing) = default; friend bool operator>(Thing, Thing) = default; friend bool operator<=(Thing, Thing) = default; friend bool operator>=(Thing, Thing) = default; };

O incluso en forma corta:

struct Thing { int a, b, c; std::string d; default: ==, !=, <, >, <=, >=; // defines the six non-member functions };

Por supuesto, todo esto puede cambiar para cuando esta propuesta sea finalmente aceptada.


C ++ 0x ha tenido una propuesta de funciones predeterminadas, por lo que podría decir default operator==; Hemos aprendido que ayuda a hacer estas cosas explícitas.


C ++ 20 proporciona una manera de implementar fácilmente un operador de comparación predeterminado.

Ejemplo de cppreference.com :

class Point { int x; int y; public: auto operator<=>(const Point&) const = default; // ... non-comparison functions ... }; // compiler generates all six relational operators Point pt1, pt2; if (pt1 == pt2) { /*...*/ } // ok std::set<Point> s; // ok s.insert(pt1); // ok if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to <=>


Conceptualmente no es fácil definir la igualdad. Incluso para los datos POD, se podría argumentar que incluso si los campos son los mismos, pero es un objeto diferente (en una dirección diferente) no es necesariamente igual. Esto realmente depende del uso del operador. Desafortunadamente su compilador no es psíquico y no puede inferir eso.

Además de esto, las funciones predeterminadas son excelentes formas de dispararse en el pie. Los valores predeterminados que describe están básicamente allí para mantener la compatibilidad con las estructuras POD. Sin embargo, causan más que suficientes estragos en los desarrolladores que se olvidan de ellos o de la semántica de las implementaciones predeterminadas.


El argumento de que si el compilador puede proporcionar un constructor de copia predeterminado, debería ser capaz de proporcionar un operator==() predeterminado similar operator==() tiene cierto sentido. Creo que la razón de la decisión de no proporcionar un valor predeterminado generado por el compilador para este operador puede ser adivinada por lo que dijo Stroustrup sobre el constructor de copia predeterminado en "El diseño y la evolución de C ++" (Sección 11.4.1 - Control de copia) :

Personalmente, considero desafortunado que las operaciones de copia estén definidas de forma predeterminada y prohíbo la copia de objetos de muchas de mis clases. Sin embargo, C ++ heredó sus constructores predeterminados de asignación y copia de C, y se utilizan con frecuencia.

Entonces, en lugar de "¿por qué C ++ no tiene un operator==() predeterminado operator==() ?", La pregunta debería haber sido "¿Por qué C ++ tiene una tarea predeterminada y un constructor de copia?", Con la respuesta de que esos elementos fueron incluidos de forma renuente por Stroustrup para compatibilidad hacia atrás con C (probablemente la causa de la mayoría de las verrugas de C ++, pero también la causa principal de la popularidad de C ++).

Para mis propios propósitos, en mi IDE, el fragmento que utilizo para las nuevas clases contiene declaraciones para un operador de asignación privado y constructor de copia, de modo que cuando genere una nueva clase no obtenga ninguna asignación predeterminada y operaciones de copia. Tengo que eliminar explícitamente la declaración. de esas operaciones desde la sección private: si quiero que el compilador pueda generarlas por mí.


El compilador no sabría si quería una comparación de puntero o una comparación profunda (interna).

Es más seguro simplemente no implementarlo y dejar que el programador lo haga ellos mismos. Entonces pueden hacer todas las suposiciones que les gustan.


En este video Alex Stepanov, el creador de STL, responde esta pregunta aproximadamente a las 13:00. Para resumir, habiendo observado la evolución de C ++, argumenta que:

  • Es desafortunado que == y! = No se declaren implícitamente (y Bjarne está de acuerdo con él). Un lenguaje correcto debe tener esas cosas listas para usted (va más allá para sugerir que no debe ser capaz de definir un ! = Que rompe la semántica de == )
  • La razón por la que este es el caso tiene sus raíces (como muchos de los problemas de C ++) en C. Allí, el operador de asignación se define implícitamente con la asignación de bit por bit, pero eso no funcionaría para == . Una explicación más detallada se puede encontrar en este article de Bjarne Stroustrup.
  • En la pregunta de seguimiento ¿ Por qué entonces no se usó una comparación miembro por miembro? Dice algo sorprendente : C era una especie de lenguaje propio y el tipo que implementaba estas cosas para Ritchie le dijo que le parecía difícil de implementar.

Luego dice que en el futuro (distante) == y ! = Se generará implícitamente.


En mi humilde opinión, no hay ninguna razón "buena". La razón por la que hay tanta gente que está de acuerdo con esta decisión de diseño es porque no aprendieron a dominar el poder de la semántica basada en el valor. La gente necesita escribir muchos constructores de copia personalizados, operadores de comparación y destructores porque usan punteros en bruto en su implementación.

Cuando se usan punteros inteligentes apropiados (como std :: shared_ptr), el constructor de copia predeterminado generalmente está bien y la implementación obvia del operador hipotético de comparación predeterminado sería tan buena.


Estoy de acuerdo, para las clases de tipo POD, entonces el compilador podría hacerlo por usted. Sin embargo, lo que podría considerar simple, el compilador podría equivocarse. Así que es mejor dejar que el programador lo haga.

Una vez tuve un caso de POD en el que dos de los campos eran únicos, por lo que una comparación nunca se consideraría verdadera. Sin embargo, la comparación que solo necesitaba en la carga útil, algo que el compilador nunca entendería o podría resolver por sí solo.

Además, no tardan mucho en escribir, ¿verdad?


Los operadores de comparación predeterminados serían correctos en una cantidad muy pequeña de tiempo; Espero que sean una fuente de problemas en lugar de algo útil.

Además, los métodos predeterminados que mencionas a menudo son indeseables. Ver código como este para deshacerse del constructor y operador de copia por defecto = es muy común:

class NonAssignable { // .... private: NonAssignable(const NonAssignable&); // Unimplemented NonAssignable& operator=(const NonAssignable&); // Unimplemented };

En una gran cantidad de código, es común ver un comentario "constructor de copia predeterminado y operador = OK" para indicar que no es un error que se hayan eliminado o definido explícitamente.


No es posible definir el valor predeterminado == , pero puede definir el valor predeterminado != través de == que normalmente debe definir usted mismo. Para esto debes hacer las siguientes cosas:

#include <utility> using namespace std::rel_ops; ... class FooClass { public: bool operator== (const FooClass& other) const { // ... } };

Puede ver http://www.cplusplus.com/reference/std/utility/rel_ops/ para más detalles.

Además, si define el operator< , se pueden deducir de él los operadores para <=,>,> = al usar std::rel_ops .

Pero debe tener cuidado al usar std::rel_ops porque los operadores de comparación se pueden deducir para los tipos para los que no se espera.

La forma más preferida de deducir un operador relacionado de uno básico es usar boost::operators .

El enfoque utilizado en boost es mejor porque define el uso del operador para la clase que solo desea, no para todas las clases en el alcance.

También puede generar "+" desde "+ =", - desde "- =", etc ... (vea la lista completa here )


Se responde que C ++ no hizo == porque C no, y aquí es por qué C proporciona solo default = pero no == en el primer lugar. C quería mantenerlo simple: C implementado = por memcpy; sin embargo, == no puede ser implementado por memcmp debido al relleno. Debido a que el relleno no está inicializado, memcmp dice que son diferentes aunque sean iguales. El mismo problema existe para la clase vacía: memcmp dice que son diferentes porque el tamaño de las clases vacías no es cero. Se puede ver desde arriba que implementar == es más complicado que implementar = en C. Algunos example código relacionados con esto. Tu corrección es apreciada si me equivoco.