c++ - tipos - ¿Es el operador de comparación de tres vías siempre eficiente?
operadores unarios c++ (3)
¿Definiría
operator>(a,b)
comoa<=>b > 0
no llevaría a grandes gastos generales?
Llevaría a algunos gastos generales. Sin embargo, la magnitud de los gastos generales es relativa: en situaciones en que los costos de realizar comparaciones son insignificantes en relación con el resto del programa, reducir la duplicación de código al implementar un operador en lugar de cinco puede ser una compensación aceptable.
Sin embargo, la propuesta no sugiere eliminar otros operadores de comparación en favor de <=>
: si desea sobrecargar a otros operadores de comparación, tiene la libertad de hacerlo:
Sea general: no restrinja lo que es inherente. No restrinja arbitrariamente un conjunto completo de usos. Evitar casos especiales y características parciales. - Por ejemplo, este documento es compatible con los siete operadores y operaciones de comparación, incluida la adición de comparación de tres vías mediante
<=>
. También es compatible con las cinco categorías de comparación principales, incluidas las órdenes parciales.
Herb Sutter, en su propuesta para el operador de "nave espacial" (sección 2.2.2, parte inferior de la página 12), dice:
Basando todo en
<=>
y su tipo de retorno: este modelo tiene grandes ventajas, algunas exclusivas de esta propuesta en comparación con las propuestas anteriores para C ++ y las capacidades de otros idiomas:[...]
(6) Eficiencia, incluyendo finalmente lograr una abstracción de cero gastos generales para las comparaciones: la gran mayoría de las comparaciones son siempre de una sola pasada. La única excepción se genera
<=
y>=
en el caso de los tipos que admiten tanto la ordenación parcial como la igualdad. Para<
, una sola pasada es esencial para lograr el principio de sobrecarga cero para evitar la repetición de comparaciones de igualdad, como para lastruct Employee { string name; /*more members*/ };
struct Employee { string name; /*more members*/ };
utilizado en lastruct Outer { Employeee; /*more members*/ };
struct Outer { Employeee; /*more members*/ };
- las comparaciones de hoy violan la abstracción de cero sobrecarga porque eloperator<
enOuter
realiza comparaciones de igualdad redundantes, porque realizaif (e != that.e) return e < that.e;
que atraviesa el prefijo igual dee.name
dos veces (y si el nombre es igual, atraviesa los prefijos iguales de otros miembros deEmployee
también), y esto no se puede optimizar en general. Como señala Kamiński, la abstracción de cero gastos generales es un pilar de C ++, y su comparación por primera vez es una ventaja significativa de este diseño basado en<=>
.
Pero luego da este ejemplo (sección 1.4.5, página 6):
class PersonInFamilyTree { // ...
public:
std::partial_ordering operator<=>(const PersonInFamilyTree& that) const {
if (this->is_the_same_person_as ( that)) return partial_ordering::equivalent;
if (this->is_transitive_child_of( that)) return partial_ordering::less;
if (that. is_transitive_child_of(*this)) return partial_ordering::greater;
return partial_ordering::unordered;
}
// ... other functions, but no other comparisons ...
};
¿Definiría operator>(a,b)
como a<=>b > 0
no llevaría a grandes gastos generales? (aunque en una forma diferente a la que él discute). Ese código probaría primero la igualdad, luego por less
, y finalmente por greater
, en lugar de solo y directamente por greater
.
¿Me estoy perdiendo de algo?
En términos generales, la sobrecarga <=>
tiene sentido cuando se trata de un tipo en el que hacer todas las comparaciones a la vez es trivialmente más costoso o tiene el mismo costo que compararlas de manera diferente.
Con cadenas, <=>
parece más caro que una prueba directa ==
, ya que debes restar cada par de dos caracteres. Sin embargo, como ya tenías que cargar cada par de caracteres en la memoria, agregar una resta además de eso es un gasto trivial. De hecho, los compiladores implementan a veces la comparación de dos números para la igualdad como una resta y una prueba contra cero. E incluso para los compiladores que no lo hacen, la resta y la comparación con cero probablemente no sea significativamente menos eficiente.
Así que para tipos básicos como esos, estás más o menos bien.
Cuando se trata de algo como la ordenación de árboles, realmente necesita saber de antemano qué operación le interesa. Si todo lo que pediste fue ==
, realmente no quieres tener que buscar en el resto del árbol solo para saber que son desiguales.
Pero personalmente ... para empezar, nunca implementaría algo como ordenar árboles con operadores de comparación. ¿Por qué? Porque creo que las comparaciones de este tipo deberían ser operaciones lógicamente rápidas. Si bien la búsqueda de un árbol es una operación tan lenta que realmente no desea hacerlo por accidente o en cualquier otro momento que no sea absolutamente necesario.
Sólo mira este caso. ¿Qué significa realmente decir que una persona en un árbol familiar es "menos que" otra? Significa que uno es hijo del otro. ¿No sería más legible en código simplemente hacer esa pregunta directamente con is_transitive_child_of
?
Cuanto más compleja sea la lógica de comparación, menos probable es que lo que estás haciendo sea realmente una "comparación". Probablemente haya alguna descripción textual de que se podría llamar a esta operación de "comparación" que sería más legible.
Oh, claro, una clase así podría tener una función que devuelve un orden partial_order
representa la relación entre los dos objetos. Pero no llamaría a esa función operator<=>
.
Pero en cualquier caso, ¿es <=>
una abstracción de comparación de gastos generales? No; puede construir casos en los que cuesta mucho más calcular la ordenación que detectar la relación específica que solicitó. Pero personalmente, si ese es el caso, hay una buena probabilidad de que no deba comparar tales tipos a través de operadores.
Para alguna definición de grande. Hay una sobrecarga porque en un pedido parcial, a == b
iff a <= b
y b <= a
. La complejidad sería la misma que una ordenación topológica, O(V+E)
. Por supuesto, el enfoque moderno de C ++ es escribir código seguro, limpio y legible y luego optimizar. Puede elegir implementar el operador de la nave espacial primero, luego especializarse una vez que determine los cuellos de botella de rendimiento.