Я слежу за книгой по изучению C ++ (из фона Python). Я написал это, и это работает:

class CatalogueItem
{
    public:
        CatalogueItem();
        CatalogueItem(int item_code, const string &name, const string &description);
        ~CatalogueItem() {};

        bool operator< (const CatalogueItem &other) const;
        ...

    private:
        ...
};

...

list<CatalogueItem> my_list;

// this is just me playing around
CatalogueItem items[2];
items[0] = CatalogueItem(4, string("box"), string("it's a box"));
items[1] = CatalogueItem(3, string("cat"), string("it's a cat"));

my_list.push_back(items[0]);
my_list.push_back(items[1]);

my_list.sort();

Часть, которую я пробую, использует оператор <, чтобы позволить списку сортировать себя.

Все это кажется хорошим, но http: //google-styleguide.googlecode. com / svn / trunk / cppguide.xml # Operator_Overloading, кажется, предлагает избегать этого, а именно это и сказано в книге! («В частности, не перегружайте operator == или operator <только для того, чтобы ваш класс можно было использовать в качестве ключа в контейнере STL; вместо этого вы должны создать типы функторов равенства и сравнения при объявлении контейнера.»)

Я понимаю, что «создание типов функторов равенства и сравнения» означает создание функций сравнения, подобных приведенной ниже:

bool my_comparison_function(const CatalogueItem &a, const CatalogueItem &b)
{
    // my comparison code here
}

Это то, о чем говорится в руководстве по стилю?

Есть ли у кого-нибудь вариант, какой способ более "правильный"?

J

4
Jessica 13 Окт 2009 в 19:57
6
Я не думаю, что руководство по стилю Google - действительно лучший набор рекомендаций по C ++. Если вы читаете его, в нескольких разделах прямо говорится, что решения были приняты по их собственным внутренним причинам, а не потому, что они «правильные».
 – 
alex tingle
13 Окт 2009 в 20:44
1
По сути, Google придерживается «бесполезного» подхода к C ++, который, возможно, необходим в организации, состоящей из тысяч талантливых программистов, которых нужно каким-то образом убедить создать взаимно понятный C ++. Но их руководство по стилю заставляет меня честно задаться вопросом, почему они вообще приняли решение использовать C ++, а не C вместе с каким-то соглашением, чтобы смоделировать эквивалент виртуальных вызовов. Программистам просто кажется жестоким раскачивать перед ними всевозможные возможности, а затем уводить их прочь ;-)
 – 
Steve Jessop
13 Окт 2009 в 21:55
Перегрузка операторов <и operator = - вполне нормальное явление. Проблема здесь в том, что люди в первые дни злоупотребляли перегрузкой операторов, и побочные эффекты часто нелегко увидеть при чтении кода. Так что будьте благоразумны.
 – 
Martin York
13 Окт 2009 в 22:50
«люди в первые дни злоупотребляли перегрузкой операторов» - не в последнюю очередь комитет по стандартам с auto_ptr.
 – 
Steve Jessop
14 Окт 2009 в 15:29
@SteveJessop: Кто-нибудь сделал подробный анализ «Руководства по стилю Google», на который я могу сослаться. Что-то, что показывает, что хорошо / плохо / некрасиво в их рекомендациях для C ++.
 – 
Martin York
25 Фев 2012 в 02:25

3 ответа

Лучший ответ

Тип функтора будет более похож на это:

struct CatalogueItemLessThan
{
    bool operator()(const CatalogueItem &a, const CatalogueItem &b)
    {
    }    
};

Тогда использование будет выглядеть так:

list<CatalogueItem> my_list;

// this is just me playing around
CatalogueItem items[2];
items[0] = CatalogueItem(4, string("box"), string("it's a box"));
items[1] = CatalogueItem(3, string("cat"), string("it's a cat"));

my_list.push_back(items[0]);
my_list.push_back(items[1]);

my_list.sort(CatalogueItemLessThan());

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

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

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

10
Community 23 Май 2017 в 15:19
Ага, вот что они имеют в виду; такие классы иногда называют компараторами.
 – 
David Seiler
13 Окт 2009 в 20:09
1
Другое главное преимущество использования функтора состоит в том, что вы можете использовать функтор в качестве параметра типа для таких шаблонов, как std::map, чего нельзя сделать с простой функцией.
 – 
Pavel Minaev
13 Окт 2009 в 20:11
1
Хорошо, но совершенно неверно! :) Вы можете использовать обычные функции в качестве параметров для классов шаблонов. Обычные функции (переходящие в пионеры функций) также являются функторами, и они отлично работают как компараторы с 'std :: map' (например). Просто попробуйте. Основное преимущество функтора на основе классов заключается в том, что вы можете присоединить к этому функтору внутреннее состояние, то есть добавить поля данных в класс. Обычные функции не могут иметь такое состояние. В противном случае нет никакой разницы между функторами на основе классов и обычными функциями.
 – 
AnT
13 Окт 2009 в 21:02
1
Роджерс: Это правда (игнорируя то, что может делать какой-нибудь сверхсовременный компилятор). Кроме того, функторы на основе классов иногда более устойчивы к неоднозначности в разрешении перегрузки. Однако это не меняет того факта, что обычные функции также являются функторами и работают нормально везде, где разрешены функторы.
 – 
AnT
13 Окт 2009 в 21:30
1
Это не то, как вы его используете. Конечно, вы должны передать шаблону тип указателя функции в качестве аргумента типа. Сам указатель функции передается конструктору в качестве аргумента. Например: std :: map map (testLess)
 – 
AnT
13 Окт 2009 в 22:20

Google пытается вам сказать следующее.

Как вы знаете, вы можете перегрузить один и только один оператор '<' для данного типа. Допустим, это работает для вас. Но представьте, что в будущем вам может потребоваться сортировать объекты одного типа в соответствии с каким-либо другим критерием сравнения. Как ты собираешься это сделать? Единственная доступная версия '<' уже занята.

Конечно, вы можете сделать это, написав новую именованную функцию / функтор сравнения (а не оператор «<») и явно предоставив ее алгоритму сортировки. Вы можете написать еще 2, 5, 10 из них. Вы можете писать сколько угодно. Это будет работать. Однако в этот момент в вашем коде будет очевидная асимметрия. Одна функция сравнения реализована как «оператор <». Остальные - как разные поименованные функции / функторы. Есть ли у этой асимметрии веская причина?

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

Однако что делать, если у вас нет столь очевидного кандидата на «естественное» сравнение? В этом случае отдавать предпочтение одному методу перед другим и «тратить» оператор '<' на произвольно выбранный - не лучшая идея. В этом случае рекомендуется оставить символ «<» в покое и вместо этого использовать именованные функции / функторы.

Другими словами, перегружая '<', вы создаете «избранное» сравнение для данного типа. Если это то, чего вы действительно хотите - продолжайте и делайте это. Но имейте в виду, что во многих случаях создание искусственного и произвольного «фаворита» - не лучшая идея. Не торопитесь с выбором фаворита. Не принимайте "<" слишком рано.

9
AnT 14 Окт 2009 в 06:54
Хорошие аргументы в пользу того, почему это рекомендация. С другой стороны, многие типы нужно сортировать только одним способом. Не так уж сложно переключиться с оператора <на функтор, если весь код принадлежит вам, основная трудность заключается в том, что вы создаете библиотеку для использования другими и не можете просто отразить изменения от компилятора.
 – 
tfinniga
13 Окт 2009 в 20:17

Тип функтора - это тип C ++ (класс или структура), который перегружает оператор (), так что экземпляры типа ведут себя как функция. Это похоже на класс, реализующий __call__() в Python.

Некоторым типам коллекций STL, таким как std::map, требуется функтор key_compare для упорядочивания ключей в структурах целого дерева и, таким образом, обеспечения быстрого доступа. По умолчанию это std::less, который использует operator< для сравнения значений. Поэтому этот оператор часто предоставляется, чтобы пользовательские классы могли действовать как ключи в std::map (и т.п.).

Очевидно, что Google не одобряет этого в пользу предоставления собственного функтора сравнения. Итак, вместо реализации operator< вы можете сделать следующее:

struct my_compare
{
    bool operator ()(const CatalogueItem& lhs, const CatalogueItem& rhs)
    {
        ...
    }
};

Если для реализации этого вам необходим доступ к закрытым членам, объявите functor как friend своего класса.

1
Ferdinand Beyer 13 Окт 2009 в 20:10
Функтор - это более общее понятие, чем то, что вы описали выше. Функтор - это все, что принимает постфиксный оператор '()'. Обычная функция или указатель на функцию также является функтором. Это не обязательно должен быть объект типа класса с перегруженным '()'.
 – 
AnT
13 Окт 2009 в 20:13
Однако, просто для придирки, я думаю, что Фердинанд прав, определяя термин «тип функтора». Он не сказал «функтор есть». +1 за добавление рекомендации друга!
 – 
Andy Dent
14 Окт 2009 в 07:07