Сравнение типов функторов и операторов <

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

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

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

Ответ 1

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

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

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

Ответ 2

Как правило, определение 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.

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

Ответ 3

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

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

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

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

}

Ответ 4

Функтор - это класс с 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;
        }
    };
};

Ответ 5

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

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

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

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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