¿Hay soporte en C ++ / STL para clasificar objetos por atributo?

Me pregunto si hay soporte en STL para esto:

Digamos que tengo una clase como esta:

class Person { public: int getAge() const; double getIncome() const; .. .. }; 

y un vector:

 vector people; 

Me gustaría ordenar el vector de personas por su edad: sé que puedo hacerlo de la siguiente manera:

 class AgeCmp { public: bool operator() ( const Person* p1, const Person* p2 ) const { return p1->getAge() getAge(); } }; sort( people.begin(), people.end(), AgeCmp() ); 

¿Hay una manera menos verbosa de hacer esto? Parece excesivo tener que definir una clase completa solo porque quiero ordenar en función de un ‘atributo’. Algo como esto tal vez?

 sort( people.begin(), people.end(), cmpfn() ); 

Adaptador genérico para comparar basado en los atributos del miembro. Si bien es bastante más detallado la primera vez, es reutilizable.

 // Generic member less than template  struct member_lt_type { typedef MT::* member_ptr; member_lt_type( member_ptr p, C c ) : ptr(p), cmp(c) {} bool operator()( T const & lhs, T const & rhs ) const { return cmp( lhs.*ptr, rhs.*ptr ); } member_ptr ptr; C cmp; }; // dereference adaptor template  struct dereferrer { dereferrer( C cmp ) : cmp(cmp) {} bool operator()( T * lhs, T * rhs ) const { return cmp( *lhs, *rhs ); } C cmp; }; // syntactic sugar template  member_lt_type > member_lt( MT::*ptr ) { return member_lt_type >(ptr, std::less() ); } template  member_lt_type member_lt( MT::*ptr, C cmp ) { return member_lt_type( ptr, cmp ); } template  dereferrer deref( C cmp ) { return dereferrer( cmp ); } // usage: struct test { int x; } int main() { std::vector v; std::sort( v.begin(), v.end(), member_lt( &test::x ) ); std::sort( v.begin(), v.end(), member_lt( &test::x, std::greater() ) ); std::vector vp; std::sort( v.begin(), v.end(), deref( member_lt( &test::x ) ) ); } 

No necesita crear una clase, solo escriba una función:

 #include  #include  using namespace std; struct Person { int age; int getage() const { return age; } }; bool cmp( const Person * a, const Person * b ) { return a->getage() < b->getage() ; } int main() { vector  v; sort( v.begin(), v.end(), cmp ); } 

Esto no es tanto una respuesta en sí misma, sino una respuesta a la respuesta de AraK a mi comentario de que ordenar con una función en lugar de un functor puede ser más lento. Este es un código de prueba (ciertamente feo, demasiado CnP) que compara varias clasificaciones: qsort, std :: sort of vector vs. array, y std :: sort usando una clase de plantilla, función de plantilla o función simple para comparar:

 #include  #include  #include  #include  int compare(void const *a, void const *b) { if (*(int *)a > *(int *)b) return -1; if (*(int *)a == *(int *)b) return 0; return 1; } const int size = 200000; typedef unsigned long ul; void report(char const *title, clock_t ticks) { printf("%s took %f seconds\n", title, ticks/(double)CLOCKS_PER_SEC); } void wait() { while (clock() == clock()) ; } template  struct cmp1 { bool operator()(T const &a, T const &b) { return a < b; } }; template  bool cmp2(T const &a, T const &b) { return a()); total += clock()- start; } report("std::sort (template class comparator)", total); total = 0; for (int i=0; i); total += clock()- start; } report("std::sort (template func comparator)", total); total = 0; for (int i=0; i array3(array1, array1+size); wait(); clock_t start = clock(); std::sort(array3.begin(), array3.end()); total += clock()-start; } report("std::sort (vector)", total); return 0; } 

Al comstackr esto con VC ++ 9 / VS 2008 usando cl /O2b2 /GL sortbench3.cpp , obtengo:

 qsort took 3.393000 seconds std::sort (array) took 1.724000 seconds std::sort (template class comparator) took 1.725000 seconds std::sort (template func comparator) took 2.725000 seconds std::sort (func comparator) took 2.505000 seconds std::sort (vector) took 1.721000 seconds 

Creo que estos se clasifican de forma bastante clara en tres grupos: el uso de ordenar con la comparación predeterminada y el uso de la clase de plantilla produjo el código más rápido. El uso de la función o la función de plantilla es claramente más lento. Usar qsort es (sorprendentemente para algunos) el más lento de todos, en torno a un margen de 2: 1.

La diferencia entre cmp2 y cmp3 parece provenir completamente de pasar por referencia vs. valor – si cambia cmp2 para tomar sus argumentos por valor, su velocidad coincide exactamente con cmp3 (al menos en mi prueba). La diferencia es que si sabes que el tipo va a ser int , casi seguro pasarás por valor, mientras que para T genérico, generalmente pasarás por referencia const (por si acaso es algo más caro de copiar).

Si hay una sola cosa por la que alguna vez va a querer ordenar personas (o si hay un valor predeterminado razonable que va a querer usar la mayor parte del tiempo), anule el operator< para que la clase People clasifique por este atributo . Sin un comparador explícito, las funciones de clasificación STL (y cualquier cosa que haga un uso implícito de ordenar, como conjuntos y mapas) usarán el operator< .

Cuando desee ordenar por algo que no sea operator< , la forma en que describe es la única manera de hacerlo a partir de la versión actual de C ++ (aunque el comparador puede ser una función normal, no tiene que ser un functor). ) El estándar C ++ 0x lo hará menos detallado permitiendo funciones lambda .

Si no está dispuesto a esperar C ++ 0x, una alternativa es usar boost :: lambda .

Veo que Dribeas ya publicó esa idea, pero como ya la escribí, así es como escribirías un comparador genérico para usar las funciones getter.

 #include  template  class CompareAttributeT: public std::binary_function { typedef ResultType (Object::*Getter)() const; Getter getter; public: CompareAttributeT(Getter method): getter(method) {} bool operator()(const Object* lhv, const Object* rhv) const { return (lhv->*getter)() < (rhv->*getter)(); } }; template  CompareAttributeT CompareAttribute(ResultType (Object::*getter)() const) { return CompareAttributeT(getter); } 

Uso:

 std::sort(people.begin(), people.end(), CompareAttribute(&Person::getAge)); 

Creo que podría ser una buena idea sobrecargar operator() para non-pointers, pero entonces uno no podría tipear los arguments_types heredando binary_function , lo que probablemente no sea una gran pérdida, ya que sería difícil usarlo donde esos son Necesario de todos modos, por ejemplo, uno simplemente no podría negar el functor de comparación de todos modos.

¡Estas respuestas son todas muy detalladas aunque me encanta la idea de la plantilla! Solo usa funciones lambda, ¡hace las cosas mucho más simples!

Podrías usar esto:

 sort( people.begin(), people.end(), []( Person a, Person b ){ return a.age < b.age; } ); 

Acabo de probar esto en base a las ideas de UncleBens y david-rodriguez-dribeas.

Esto parece funcionar (como está) con mi comstackdor actual. g ++ 3.2.3. Por favor, avíseme si funciona en otros comstackdores.

 #include  #include  #include  using namespace std; class Person { public: Person( int _age ) :age(_age) { } int getAge() const { return age; } private: int age; }; template  class get_lt_type { ResType (T::*getter) () const; public: get_lt_type(ResType (T::*method) () const ):getter(method) {} bool operator() ( const T* pT1, const T* pT2 ) const { return (pT1->*getter)() < (pT2->*getter)(); } }; template  get_lt_type get_lt( ResType (T::*getter) () const ) { return get_lt_type( getter ); } int main() { vector people; people.push_back( new Person( 54 ) ); people.push_back( new Person( 4 ) ); people.push_back( new Person( 14 ) ); sort( people.begin(), people.end(), get_lt( &Person::getAge) ); for ( size_t i = 0; i < people.size(); ++i ) { cout << people[i]->getAge() << endl; } // yes leaking Persons return 0; } 

Puede tener solo una función global o una función estática. Cada una de estas funciones globales o estáticas se compara con un atributo. No es necesario hacer una clase. Una forma de mantener papeters para comparar es usar boost bind, pero bind solo es útil para encontrar todas las clases o comparar todas las clases con algún parámetro enlazado. Almacenar datos en múltiples elementos es la única razón para hacer un functor.

Editar: también vea boost las funciones de lambda, pero solo son prácticas para funciones simples.