Различия между различными пользовательскими функциями компаратора в C++


Я обнаружил, что существуют различные способы определения пользовательских функций сравнения для пользовательского объекта. Я хотел бы знать, что я должен принять во внимание, прежде чем выбрать одно из них.

Если у меня есть объект student, я могу написать пользовательскую функцию сравнения следующими способами.

struct Student
{
    string name;
    uint32_t age;

    // Method 1: Using operator <
    bool operator<(const Student& ob)
    {
        return age < ob.age;
    }
};

// Method 2: Custom Compare Function
bool compStudent(const Student& a, const Student& b)
{
    return a.age < b.age;
}

// Method 3: Using operator ()
struct MyStudComp
{
    bool operator() (const Student& a, const Student& b)
    {
        return a.age < b.age;
    }
}obComp;

Для сортировки вектора студентов я могу использовать любой из следующих методов.

vector<Student> studs; // Consider I have this object populated
std::sort(studs.begin(), studs.end());  // Method 1
std::sort(studs.begin(), studs.end(), compStudent);    // Method 2
std::sort(studs.begin(), studs.end(), obComp);  // Method 3

// Method 4: Using Lambda
sort(studs.begin(), studs.end(), 
     [](const Student& a, const Student& b) -> bool
     { 
        return a.age < b.age; 
     });

Чем отличаются эти методы и как я должен выбирать между ними. Спасибо продвижение.

4 2

4 ответа:

Производительность между различными методами не очень отличается, однако использование < позволит вам быть более гибким и значительно упростит использование встроенных модулей. Я также думаю, что использование () Немного странно.

Более серьезная проблема в вашем примере заключается в том, что ваши методы должны использовать const refs вместо значений. То есть bool operator<(Student ob) может быть friend bool operator<(const Student& ls, const Student& rs){...}. Кроме того, смотрите здесь для некоторых примеров различных вещей, которые следует учитывать при перегрузке операторов.

Производительность не будет заметно отличаться. Но во многих случаях удобно (и ожидаемо) иметь operator<, поэтому я бы пошел на это через специальную функцию сравнения.

На самом деле нет "правильного" способа как такового, но если для вашего объекта имеет смысл иметь пользовательские компараторы (т. е. operator< и т. д.) тогда было бы разумно просто использовать их. Однако вы можете захотеть отсортировать ваш объект на основе другого члена поля, и поэтому предоставление пользовательского лямбда-кода на основе этих сравнений полей будет иметь смысл в этом случае.

Например, ваш Student класс В настоящее время использует перегруженный operator< Для сравнения возрастов учащихся, поэтому, если вы сортируете контейнер Students на основе возраст тогда просто используйте этот оператор неявно. Тем не менее, вы можете захотеть (в другое время) выполнить сортировку на основе имен, чтобы в этом случае вы могли предоставить пользовательский лямбда-код в качестве наиболее элегантного метода:

std::vector<Student> vec;
// populate vec
std::sort(vec.begin(), vec.end(), [](auto& lhs, auto& rhs) { return lhs.name < rhs.name; });
Где имена учащихся сортируются с помощью лексикографических сравнений.

Чем отличаются эти методы и как я должен выбирать между ними.

Они различаются своими имплицитными заявлениями о намерениях. Вы должны использовать форму, которая наиболее лаконично выражает ваше намерение.

Полагаться на operator< означает для кого-то, читающего ваш код, что ваши объектынеявно упорядочены , как числа или строки. Они должны быть вещами, которые люди сказали бы: "ну, очевидно, x приходит раньше y".

Если упорядоченность карты больше абстрактно, тогда функция упорядочения может быть лучше, потому что она выражает идею о том, что вы устанавливаете порядок на карте, который может не быть естественным порядком.

В приведенном примере я мог бы выразить намерение в любом функциональном объекте, называемом ageIsLess, например. Как читатель кода, использующий карту, теперь полностью осознает намерение.

Например:

#include <cstdint>
#include <set>
#include <string>
#include <algorithm>
#include <iterator>

struct Student
{
    std::string name;
    std::uint32_t age;

};

struct ByAscendingAge
{
    bool operator() (const Student& a, const Student& b) const
    {
        return a.age < b.age;
    }
};


bool age_is_less(const Student& l, const Student& r)
{
  return l.age < r.age;
};

bool name_is_less(const Student& l, const Student& r)
{
  return l.name < r.name;
};

int main()
{

  // this form expresses the intent that any 2 different maps of this type can have different ordering

  using students_by_free_function = std::set<Student, bool (*)(const Student&, const Student&)>;

  // ordered by age
  students_by_free_function by_age_1(age_is_less);

  // ordered by name
  students_by_free_function by_name_1(name_is_less);

  // above two maps are the same type so we can assign them, which implicitly reorders
  by_age_1 = by_name_1;


  // this form expresses the intent that the ordering is a PROPERTY OF THIS TYPE OF SET
  using students_by_age = std::set<Student, ByAscendingAge>;

  // note that we don't need a comparator in the constructor
  students_by_age by_age_2;

  // by_age_2 = by_age_1;  // not allowed because the sets are a different type

  // but we can assign iterator ranges of course
  std::copy(std::begin(by_age_1), 
            std::end(by_age_1), 
            std::inserter(by_age_2, 
                          std::end(by_age_2)));


}