c++ - Tipos de Funcional Comparación comparados con operador<
stl coding-style (7)
En la Guía de estilo de Google C ++ , la sección sobre Sobrecarga del operador no recomienda sobrecargar a los operadores ("excepto en circunstancias excepcionales especiales"). Específicamente, recomienda:
En particular, no sobrecargue
operator==
uoperator<
solo para que su clase pueda usarse como clave en un contenedor STL; en su lugar, debe crear tipos de funtores de igualdad y comparación al declarar el contenedor.
Estoy un poco confundido acerca de cómo sería un functor, pero mi pregunta principal es, ¿por qué querrías escribir tus propios funtores para esto? ¿No sería más simple definir el operator<
y usar la función estándar std::less<T>
? ¿Hay alguna ventaja al usar una sobre la otra?
Bueno, de acuerdo con la página web que citan, no hay mucha ventaja para los funtores ("[los operadores] pueden engañar a nuestra intuición y pensar que las costosas operaciones son operaciones baratas e integradas").
Mi derrota es que uno debe esforzarse por hacer que sus clases sean objetos de primera clase tanto como sea posible, lo cual, para mí, significa hacer que comprendan tantos operadores como sea sensato.
Ha pasado un tiempo desde que escribí un functor, pero se vería algo así:
class MyClass {....}
class LessThanMyClass : std:binary_function<MyClass, MyClass, bool>
{
public bool operator()(MyClass lhs, MyClass rhs)
{ return /* determine if lhs < rhs */ ; }
}
vector<MyClass> objs;
std::sort(objs.begin(), objs.end(), LessThanMyClass());
}
Creo que el mensaje detrás de no definir operator <es que ordenar es una propiedad de la colección, no del objeto. Las diferentes colecciones de los mismos objetos pueden tener diferentes ordenamientos. Por lo tanto, debe usar un functor separado para especificar el tipo de colección en lugar del operador <.
Sin embargo, en la práctica, muchas de sus clases pueden tener un orden natural y ese es el único pedido utilizado en las colecciones de su aplicación. En otros casos, el pedido puede no ser relevante para la aplicación, solo la colección para que pueda encontrar los artículos más adelante. En estos casos, tiene mucho sentido definir operator <.
Recuerde, cuando diseñamos modelos de objetos, solo modelamos un subconjunto del mundo real. En el mundo real, puede haber innumerables formas de clasificar objetos de la misma clase, pero en el dominio de la aplicación en el que trabajamos puede haber una que sea relevante.
Si el código evoluciona para necesitar un segundo pedido que sea tan relevante como el primero, la clase debe refactorizarse para eliminar el operador <y colocar ambas funciones de clasificación en functores separados. Esto muestra la intención de que ninguna clasificación es más importante que las otras.
Con respecto a los operadores aritméticos, no debe sobrecargarlos a menos que esté implementando un tipo aritmético.
Por supuesto, hay excepciones para cada regla. Si no sabe si debe hacer una excepción o no, probablemente no debería hacerlo. La experiencia será tu guía.
Excepto por los tipos más fundamentales, la operación menor que no siempre es trivial, e incluso la igualdad puede variar de una situación a otra.
Imagine la situación de una aerolínea que quiere asignar a todos los pasajeros un número de embarque. Este número refleja el orden de embarque (por supuesto). Ahora, ¿qué determina quién viene antes de quién? Puede tomar el orden en el que los clientes se registraron; en ese caso, la operación inferior compararía los tiempos de check-in. También podría considerar el precio que los clientes pagaron por sus boletos, menos de lo que ahora compararía los precios de los boletos.
… y así. En general, no es significativo definir un operator <
en la clase Passenger
, aunque puede ser necesario tener pasajeros en un contenedor clasificado. Creo que eso es contra lo que Google advierte.
En general, definir operator<
es mejor y más simple.
El caso en el que desearía utilizar funtores es cuando necesita múltiples formas de comparar un tipo particular. Por ejemplo:
class Person;
struct CompareByHeight {
bool operator()(const Person &a, const Person &b);
};
struct CompareByWeight {
bool operator()(const Person &a, const Person &b);
};
En este caso, puede que no haya una buena forma "predeterminada" de comparar y ordenar personas, por lo que no definir operator<
y utilizar funtores puede ser mejor. También podría decir que, en general, las personas se ordenan por altura, por lo que el operator<
simplemente llama a CompareByHeight
, y cualquier persona que necesite que se ordene por peso a una persona tiene que usar CompareByWeight
explícitamente.
Muchas veces el problema es que la definición de los funtores se deja al usuario de la clase, por lo que se tiende a obtener muchas redefiniciones de la misma cosa, siempre que la clase necesite ser utilizada en un contenedor ordenado.
Irónicamente, un functor también requiere reemplazar a un operador (el operador de llamada de función - operator ()
), por lo que no estoy seguro de cuál es su punto.
Probablemente no llegaría tan lejos como la guía de estilo de Google.
Creo que lo que están consiguiendo es que cuando sobrecargue operator<
y operator==
, está tomando una decisión para cada uso del tipo, mientras que los tipos de functor solo se aplican a esa colección.
Si lo único que necesita para los comparadores es colocar el elemento en una colección, entonces es mejor tener una función específica para ese contexto en lugar de los operadores que se aplicarían en todos los contextos.
Es posible que desee ordenar las órdenes de compra cronológicamente, pero, en general, tendría sentido compararlas por su precio total. Si sobrecargamos el operator<
para comparar las fechas para que podamos cargarlas en una colección, estamos introduciendo el riesgo de que otro cliente pueda hacer un mal uso de nuestro operator<
lo que puede pensar que compara los precios totales.
Un functor es una clase con un operator ()
. En este caso, el método tomaría dos parámetros del tipo que se está comparando y devolverá un resultado de bool
si el primero es menor que el segundo.
Editar: para construir sobre lo que dijo James Curran , puedes definir tu functor dentro de la clase. Así por ejemplo:
class MyClass
{
struct LessThan : public std::binary_function<MyClass, MyClass, bool>
{
bool operator()(const MyClass & first, const MyClass & second) const
{
return first.key < second.key;
}
};
};