Типы функторов сравнения и оператор < - PullRequest
10 голосов
/ 08 октября 2008

В Руководстве по стилю Google C ++ раздел о перегрузке операторов рекомендует не перегружать любых операторов («за исключением редких особых случаев»). В частности, он рекомендует:

В частности, не перегружать operator== или operator< просто так, чтобы Ваш класс может быть использован в качестве ключа в Контейнер STL; вместо этого вы должны создать функтор равенства и сравнения типы при объявлении контейнера.

Я немного размышляю о том, как будет выглядеть такой функтор, но мой главный вопрос: почему вы хотите написать для этого свои собственные функторы? Не проще ли определить operator< и использовать стандартную функцию std::less<T>? Есть ли преимущество в использовании одного над другим?

Ответы [ 7 ]

16 голосов
/ 08 октября 2008

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

Представьте себе ситуацию с авиакомпанией, которая хочет назначить всем пассажирам посадочный номер. Это число отражает порядок посадки (конечно). Теперь, что определяет, кто, прежде чем кто? Вы можете просто принять заказ, в котором зарегистрированы клиенты - в этом случае операция «меньше, чем» будет сравнивать время регистрации. Вы также можете учитывать цену, которую клиенты заплатили за свои билеты - меньше, чем сейчас сравнивают цены на билеты.

… и так далее. В общем, просто не имеет смысла определять operator < для класса Passenger, хотя может потребоваться, чтобы пассажиры были в отсортированном контейнере. Я думаю, что это то, против чего Google предупреждает.

6 голосов
/ 08 октября 2008

Как правило, определение operator< лучше и проще.

Случай, когда вы захотите использовать функторы, - это когда вам нужно несколько способов сравнения определенного типа. Например:

class Person;

struct CompareByHeight {
    bool operator()(const Person &a, const Person &b);
};

struct CompareByWeight {
    bool operator()(const Person &a, const Person &b);
};

В этом случае может не быть хорошего способа «по умолчанию» сравнивать и упорядочивать людей, поэтому лучше не определять operator< и использовать функторы. Вы также можете сказать, что обычно люди упорядочены по росту, и поэтому operator< просто звонит CompareByHeight, и любой, кому нужно, чтобы люди были упорядочены по весу, должен явно использовать CompareByWeight.

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

5 голосов
/ 08 октября 2008

Что ж, согласно веб-странице, на которую вы ссылаетесь, функторы не имеют большого преимущества («[операторы] могут обмануть нашу интуицию, полагая, что дорогие операции - это дешевые, встроенные операции»)

Я чувствую, что нужно стремиться к тому, чтобы вы как можно больше занимались с первоклассными объектами, что для меня означает, что они понимают как можно больше операторов.

Прошло много времени с тех пор, как я написал функтор, но это будет выглядеть примерно так:

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());

}

3 голосов
/ 09 октября 2008

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

Однако на практике у многих ваших классов может быть естественный порядок, и это единственный порядок, используемый в коллекциях в вашем приложении. В других случаях порядок может даже не относиться к приложению, а только к коллекции, чтобы он мог найти элементы позже. В этих случаях имеет смысл определить оператор <. </p>

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

Если для кода требуется второй порядок, который так же важен, как и первый, класс следует реорганизовать для удаления оператора <и размещения обеих функций ранжирования в отдельных функторах. Это свидетельствует о том, что ни один рейтинг не важнее других. </p>

Что касается арифметических операторов, вам не следует перегружать их, если вы не используете арифметический тип.

Конечно, есть исключения из каждого правила. Если вы не знаете, следует ли делать исключение, вы, вероятно, не должны этого делать. Опыт будет вашим гидом.

3 голосов
/ 08 октября 2008

Возможно, я бы не пошел так далеко, как руководство по стилю Google.

Я думаю, что они получают то, что, когда вы перегружаете operator< и operator==, вы принимаете решение для каждого использования типа, в то время как типы функторов применяются только к этой коллекции.

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

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

2 голосов
/ 08 октября 2008

Функтор - это класс с operator (). В этом случае метод принимает два параметра сравниваемого типа и возвращает результат bool, если первый меньше второго.

Редактировать: основываясь на том, что сказал Джеймс Керран , вы можете определить свой функтор внутри класса. Так, например:

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;
        }
    };
};
1 голос
/ 08 октября 2008

Как ни странно, функтор также требует переопределения оператора (оператор вызова функции - operator ()), поэтому я не уверен, в чем их смысл.

...