Soy un gran admirador de dejar que el compilador haga el mayor trabajo posible por usted. Al escribir una clase simple, el compilador puede darle lo siguiente de forma gratuita:
- Un constructor (vacío) predeterminado
- Un constructor de copias
- Un destructor
- Un operador de asignación (
operator=
)
Pero parece que no puede darle ningún operador de comparación, como operator==
o 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, entonces querrás tener cuidado, pero para una clase simple, ¿seguramente el compilador podría hacer esto por ti?
==
, de la misma manera que hay una asignación automática predeterminada (=
) bajo ciertas condiciones. (El argumento sobre los punteros es inconsistente porque la lógica se aplica tanto para=
y==
, y no solo para el segundo).Respuestas:
El compilador no sabría si desea una comparación de puntero o una comparación profunda (interna).
Es más seguro no implementarlo y dejar que el programador lo haga por sí mismo. Luego pueden hacer todas las suposiciones que deseen.
fuente
operator=
) generalmente funcionan en el mismo contexto que los operadores de comparación, es decir, existe la expectativa de que, después de realizara = b
,a == b
sea cierto. Definitivamente tiene sentido que el compilador proporcione un valor predeterminadooperator==
utilizando la misma semántica de valor agregado que paraoperator=
. Sospecho que paercebal es realmente correcto aquí, ya queoperator=
(y el copiador) se proporcionan únicamente por compatibilidad con C, y no querían empeorar la situación.El argumento de que si el compilador puede proporcionar un constructor de copia predeterminado, debería ser capaz de proporcionar un valor 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 se puede adivinar por lo que dijo Stroustrup sobre el constructor de copia predeterminado en "El diseño y evolución de C ++" (Sección 11.4.1 - Control de copia) :Entonces, en lugar de "¿por qué C ++ no tiene un valor predeterminado
operator==()
?", La pregunta debería haber sido "¿por qué C ++ tiene una asignación predeterminada y un constructor de copia?", Con la respuesta de que Stroustrup incluyó esos elementos de mala gana para la compatibilidad con C (probablemente la causa de la mayoría de las verrugas de C ++, pero también probablemente la razón principal de la popularidad de C ++).Para mis propios fines, en mi IDE, el fragmento que uso para las nuevas clases contiene declaraciones para un operador de asignación privado y un constructor de copia, de modo que cuando genero una nueva clase no obtengo operaciones predeterminadas de asignación y copia: tengo que eliminar explícitamente la declaración de esas operaciones de la
private:
sección si quiero que el compilador pueda generarlas por mí.fuente
Foo(const Foo&) = delete; // no copy constructor
yFoo& Foo=(const Foo&) = delete; // no assignment operator
struct
, pero deseo que deje declass
comportarse de manera diferente (y sensata). En el proceso, también habría dado una diferencia más significativa entrestruct
yclass
al lado del acceso predeterminado.operator==
. En este punto, es solo azúcar de sintaxis para algún código de placa de caldera. Si tiene miedo de que de esta manera el programador pueda pasar por alto algún puntero entre los campos de clase, puede agregar una condición de que solo puede funcionar en tipos primitivos y objetos que tienen operadores de igualdad. Sin embargo, no hay ninguna razón para no permitir esto por completo.Incluso en C ++ 20, el compilador aún no generará implícitamente
operator==
para ustedPero va a adquirir la capacidad de forma explícita por defecto
==
ya que C ++ 20 :El valor predeterminado se
==
realiza en función de los miembros==
(del mismo modo que el constructor de copias predeterminado realiza la construcción de copias en función de los miembros). Las nuevas reglas también proporcionan la relación esperada entre==
y!=
. Por ejemplo, con la declaración anterior, puedo escribir ambos:Esta característica específica (valor predeterminado
operator==
y simetría entre==
y!=
) proviene de una propuesta que era parte de la característica de lenguaje más amplia que esoperator<=>
.fuente
= default
, para lo que no se crea de forma predeterminada, ¿verdad? A mí me parece un oxímoron ("valor predeterminado explícito").En mi humilde opinión, no hay una "buena" razón. La razón por la que hay tantas personas que están 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. Las personas necesitan escribir muchos constructores de copias personalizadas, operadores de comparación y destructores porque usan punteros sin procesar en su implementación.
Cuando se utilizan punteros inteligentes apropiados (como std :: shared_ptr), el constructor de copia predeterminado generalmente está bien y la implementación obvia del operador de comparación por defecto hipotético sería tan buena.
fuente
Se respondió que C ++ no hizo == porque C no lo hizo, y esta es la razón por la cual C proporciona solo default = pero no == en 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 a pesar de que son 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. Algún ejemplo de código con respecto a esto. Su corrección es apreciada si me equivoco.
fuente
operator=
, eso solo funcionaría para los tipos de POD, pero C ++ también proporciona un valor predeterminadooperator=
para los tipos que no son POD.En esto video Alex Stepanov, el creador de STL aborda esta misma pregunta aproximadamente a las 13:00. En resumen, después de observar la evolución de C ++, argumenta que:
Luego dice que en el futuro (distante) == y ! = Se generarán implícitamente.
fuente
C ++ 20 proporciona una manera de implementar fácilmente un operador de comparación predeterminado.
Ejemplo de cppreference.com :
fuente
Point
como un ejemplo para un pedido operación, ya que no hay manera predeterminada razonable para dos puntos conx
yy
coordenadas ...std::set
para asegurarse de que todos los puntos sean únicos y solo losstd::set
useoperator<
.auto
: para este caso, ¿podemos suponer siempre que serástd::strong_ordering
de#include <compare>
?std::common_comparison_category_t
, que para esta clase se convierte en el orden predeterminado (std::strong_ordering
).No es posible definir el valor predeterminado
==
, pero puede definir el valor predeterminado!=
mediante el==
cual generalmente debería definirse a sí mismo. Para esto debes hacer lo siguiente:Puede ver http://www.cplusplus.com/reference/std/utility/rel_ops/ para más detalles.
Además, si define
operator<
, los operadores para <=,>,> = pueden deducirse de él cuando se usastd::rel_ops
.Pero debes tener cuidado cuando uses
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 el operador relacionado del básico es usar boost :: operadores .
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 aquí )
fuente
!=
después de escribir el==
operador. O lo hice pero le faltabaconst
ness. Tuve que escribirlo yo también y todo estuvo bien.rel_ops
quedó en desuso en C ++ 20: porque no funciona , al menos no en todas partes, y ciertamente no de manera consistente. No hay una forma confiablesort_decreasing()
de compilar. Por otro lado, Boost.Operators funciona y siempre ha funcionado.C ++ 0x
hatenido una propuesta para funciones predeterminadas, por lo que podría decirdefault operator==;
que hemos aprendido que ayuda a hacer estas cosas explícitas.fuente
operator==
. Lo cual es una pena.Conceptualmente no es fácil definir la igualdad. Incluso para los datos de POD, se podría argumentar que incluso si los campos son iguales, pero es un objeto diferente (en una dirección diferente) no es necesariamente igual. Esto realmente depende del uso del operador. Lamentablemente, 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 de POD. Sin embargo, causan estragos más que suficientes ya que los desarrolladores se olvidan de ellos o de la semántica de las implementaciones predeterminadas.
fuente
int
creado a través de copiador de otro es igual al que se creó; lo único lógico que hacer para unostruct
de los dosint
campos es trabajar exactamente de la misma manera.+
operador en que no es asociativo para flotantes; eso es(x + y) + z
! =x + (y + z)
, debido a la forma en que se produce el redondeo de FP. (Podría decirse que este es un problema mucho peor que==
porque es cierto para los valores numéricos normales). Podría sugerir agregar un nuevo operador de suma que funcione para todos los tipos numéricos (incluso int) y sea casi exactamente igual+
pero asociativo ( de algun modo). Pero entonces estaría agregando hinchazón y confusión al lenguaje sin realmente ayudar a tanta gente.Puede que no sea un problema funcional, pero en términos de rendimiento, la comparación predeterminada miembro por miembro puede ser más subóptima que la asignación / copia predeterminada miembro por miembro. A diferencia del orden de asignación, el orden de comparación afecta el rendimiento porque el primer miembro desigual implica que se puede omitir el resto. Entonces, si hay algunos miembros que generalmente son iguales, desea compararlos en último lugar, y el compilador no sabe qué miembros tienen más probabilidades de ser iguales.
Considere este ejemplo, donde
verboseDescription
se selecciona una cadena larga de un conjunto relativamente pequeño de descripciones meteorológicas posibles.(Por supuesto, el compilador tendría derecho a ignorar el orden de las comparaciones si reconoce que no tienen efectos secundarios, pero presumiblemente aún tomaría su cola del código fuente donde no tiene mejor información propia).
fuente
Solo para que las respuestas a esta pregunta permanezcan completas a medida que pasa el tiempo: desde C ++ 20 se puede generar automáticamente con el comando
auto operator<=>(const foo&) const = default;
Generará todos los operadores: ==,! =, <, <=,> Y> =, consulte https://en.cppreference.com/w/cpp/language/default_comparisons para más detalles.
Debido a la apariencia del operador
<=>
, se llama operador de nave espacial. Vea también ¿Por qué necesitamos el operador de nave espacial <=> en C ++?.EDIT: También en C ++ 11 un sustituto ordenada bastante para que esté disponible con
std::tie
ver https://en.cppreference.com/w/cpp/utility/tuple/tie para un ejemplo de código completo conbool operator<(…)
. La parte interesante, cambiada para trabajar==
es:std::tie
funciona con todos los operadores de comparación y el compilador lo optimiza por completo.fuente
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. Por lo tanto, es mejor dejar que el programador lo haga.
Una vez tuve un caso POD donde dos de los campos eran únicos, por lo que una comparación nunca se consideraría verdadera. Sin embargo, la comparación que necesitaba solo se comparó con la carga útil, algo que el compilador nunca entendería o podría resolver por sí mismo.
Además, no tardan mucho en escribir, ¿verdad?
fuente