88. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
88. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
Резюме
Предпочтительно передавать алгоритмам функциональные объекты, а не функции, а компараторы ассоциативных контейнеров просто должны быть функциональными объектами. Функциональные объекты адаптируемы и, вопреки ожиданиям, обычно дают более быстрый по сравнению с функциями код.
Обсуждение
Во-первых, функциональные объекты легко сделать адаптируемыми (и такими их и следует делать — см. рекомендацию 89). Даже если у вас есть готовая функция, иногда для ее использования требуется "обертка" из ptr_fun или mem_fun. Например, такая обертка требуется при построении более сложных выражений с использованием связывателей (см. также рекомендацию 84):
inline bool isHeavy(const Thing&) { /* ... */ }
find_if(v.begin(), v.end(), not1(IsHeavy)); // Ошибка
Обойти эту ошибку обычно можно путем применения ptr_fun (или, в случае функции-члена, mem_fun или mem_fun_ref), что, к сожалению, не работает в данном конкретном случае:
inline bool IsHeavy(const Thing&) { /* ... */ }
find_if(v.begin(), v.end(),
not1(ptr_fun(IsHeavy))); // Героическая попытка...
Беда в том, что этот способ не будет работать, даже если вы явно укажете аргументы шаблона ptr_fun. Коротко говоря, проблема в том, что ptr_fun точно выводит типы аргументов и возвращаемый тип (в частности, тип параметра будет выведен как const Thing&) и создает внутренний механизм, который, в свою очередь, пытается добавить другой &, а ссылка на ссылку в настоящее время в C++ не разрешена. Имеются способы исправлений языка и/или библиотека для решения данной проблемы (например, позволяя ссылке на ссылку свернуться в обычную ссылку; см. также рекомендацию 89), но на сегодняшний день проблема остается нерешенной.
Прибегать ко всем этим ухищрениям совершенно излишне, если у вас имеется корректно написанный функциональный объект (см. рекомендацию 89), который можно использовать без какого-либо специального синтаксиса:
struct IsHeavy : unary_function<Thing, bool> {
bool operator()(const Thing&) const { /* ... */ }
};
find_if(v.begin(), v.end(), not1(IsHeavy())) ; // OK
Еще более важно то, что для определения сравнения в ассоциативных контейнерах вам нужен именно функциональный объект, а не функция. Это связано с тем, что нельзя инстанцировать шаблон с функцией в качестве параметра:
bool CompareThings(const Thing&, const Thing&);
set<Thing, CompareThings> s; // Ошибка
Вместо этого следует написать:
struct CompareThings
: public binary_function<Thing,Thing,bool> {
bool operator()( const Thing&, const Thing& ) const;
};
set<Thing, CompareThings> s; //OK
Наконец, имеется еще одно преимущество функциональных объектов — эффективность. Рассмотрим следующий знакомый алгоритм:
template<typename Iter, typename Compare>
Iter find_if(Iter first, Iter last, Compare comp);
Если мы передадим алгоритму в качестве компаратора функцию
inline bool Function(const Thing&) { /* ... */ }
find_if(v.begin(), v.end(), Function);
то на самом деле будет передана ссылка на функцию. Компиляторы редко встраивают вызовы таких функций (за исключением некоторых относительно свежих компиляторов, которые в состоянии провести анализ всей программы в целом), даже если они объявлены как таковые и видимы в момент компиляции вызова find_if. Кроме того, как уже упоминалось, функции не адаптируемы.
Давайте передадим алгоритму find_if в качестве компаратора функциональный объект:
struct FunctionObject : unary_function<Thing, bool> {
bool operator()(const Thing&) const { /* ... */ }
};
find_if(v.begin(), v.end(), FunctionObject());
Если мы передаем объект, который имеет (явно или неявно) встраиваемый оператор operator(), то такие вызовы компиляторы С++ способны делать встраиваемыми уже очень давно.
Примечание. Эта методика не является преждевременной оптимизацией (см. рекомендацию 8); ее следует рассматривать как препятствие преждевременной пессимизации (см. рекомендацию 9). Если у вас имеется готовая функция — передавайте указатель на нее (кроме тех ситуаций, когда вы должны обязательно обернуть ее в ptr_fun или mem_fun). Но если вы пишете новый код для использования в качестве аргумента алгоритма, то лучше сделать его функциональным объектом.
Ссылки
[Austern99] §4, §8, §15 • [Josuttis99] §5.9 • [Meyers01] §46 • [Musser01] §8 • [Sutter04] §25
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Когда лучше использовать данные, связанные с процессорами
Когда лучше использовать данные, связанные с процессорами Использование данных, связанных с процессорами, позволяет получить ряд преимуществ. Во-первых, это ослабление требований по использованию блокировок. В зависимости от семантики доступа к данным, которые связаны
Что такое базовая станция и когда ее лучше использовать
Что такое базовая станция и когда ее лучше использовать В большинстве случаев базовая станция вам не понадобится. С помощью программы Roger Wilco вы запросто сможете создать канал для общения или подключиться к уже работающему каналу, где бы он ни находился: в Интернете или
В какой момент лучше использовать технологии допродаж
В какой момент лучше использовать технологии допродаж Всегда! В интернет-магазине существует три момента, когда это можно сделать, – до покупки в момент выбора товаров, во время покупки в корзине с товарами и после покупки до момента оплаты
31. Не пишите код, который зависит от порядка вычислений аргументов функции
31. Не пишите код, который зависит от порядка вычислений аргументов функции РезюмеПорядок вычисления аргументов функции не определен, поэтому никогда не полагайтесь на то, что аргументы будут вычисляться в той или иной очередности.ОбсуждениеНа начальных этапах развития
89. Корректно пишите функциональные объекты
89. Корректно пишите функциональные объекты РезюмеРазрабатывайте функциональные объекты так, чтобы их копирование выполнялось как можно эффективнее. Там, где это возможно, делайте их максимально адаптируемыми путем наследования от unary_function или
Правило 4: Прежде чем использовать объекты, убедитесь, что они инициализированы
Правило 4: Прежде чем использовать объекты, убедитесь, что они инициализированы Отношение C++ к инициализации значений объектов может показаться странным. Например, если вы пишете:int x;то в некоторых контекстах переменная x будет гарантированно инициализирована нулем, а в
Совет 44. Используйте функции контейнеров вместо одноименных алгоритмов
Совет 44. Используйте функции контейнеров вместо одноименных алгоритмов Некоторые контейнеры содержат функции, имена которых совпадают с именами алгоритмов STL. Так, в ассоциативных контейнерах существуют функции count, find, lower_bound, upper_bound и equal_range, а в контейнере list
Структуры в качестве аргументов функции
Структуры в качестве аргументов функции В не расширенном языке Си можно передавать функции адрес структуры. Например, если montana является структурной переменной структурного типа player, мы можем обратиться к функции следующим образом: stats(&montana);Функция stats( ) будет иметь
Функциональные объекты
Функциональные объекты Функциональные объекты - это объекты, для которых определён operator(). Они важны для эффективного использования библиотеки. В местах, где ожидается передача указателя на функцию алгоритмическому шаблону, интерфейс установлен на приём объекта с
Вызов функции с переменным числом аргументов
Вызов функции с переменным числом аргументов Для вызова функции с переменным числом аргументов не требуется никаких специальных действий: в вызове функции просто задается то число аргументов, которое нужно. В предварительном объявлении (если оно есть) переменное число
Функции работы со списком аргументов
Функции работы со списком аргументов Функция Краткое описание va_arg выбрать аргумент из списка va_end переустановить указатель va_start установить указатель на начало списка аргументов Эти макроопределения дают возможность получить доступ к аргументам функции, когда
8.2. Глобальные объекты и функции
8.2. Глобальные объекты и функции Объявление функции в глобальной области видимости вводит глобальную функцию, а объявление переменной – глобальный объект. Глобальный объект существует на протяжении всего времени выполнения программы. Время жизни глобального
12.3. Объекты-функции
12.3. Объекты-функции Наша функция min() дает хороший пример как возможностей, так и ограничений механизма шаблонов:template typename Typeconst Type&min( const Type *p, int size ){Type minval = p[ 0 ];for ( int ix = 1; ix size; ++ix )if ( p[ ix ] minval )minval = p[ ix ];return minval;}Достоинство этого механизма – возможность определить
12.3.1. Предопределенные объекты-функции
12.3.1. Предопределенные объекты-функции Предопределенные объекты-функции подразделяются на арифметические, логические и сравнительные. Каждый объект – это шаблон класса, параметризованный типами операндов. Для использования любого из них необходимо включить
12.3.3. Сравнительные объекты-функции
12.3.3. Сравнительные объекты-функции Сравнительные объекты-функции поддерживают операции равенства, неравенства, больше, больше или равно, меньше, меньше или равно.equal_tostring stringEqual;sres = stringEqual( sval1, sval2 );ires = count_if( svec.begin(), svec.end(),Равенство:equal_toType* equal_tostring(), sval1 );not_equal_tocomplex
13.3.1. Когда использовать встроенные функции-члены
13.3.1. Когда использовать встроенные функции-члены Обратите внимание, что определения функций home(), get(), height() и width() приведены прямо в теле класса. Такие функции называются встроенными. (Мы говорили об этом в разделе 7.6.)Функции-члены можно объявить в теле класса встроенными и