- Модульный способ вызова функции предиката
- Пример 6.5. Использование объектов-функций
- 6.4.2. Предикаты. Пример 6.6 (использование предикатов)
- Функторы, предикаты, функциональные адаптеры, лямбда-функции
- Вступление
- Функторы
- Предикаты
- Функциональные адаптеры
- Лямбда-функции
- Подведение итогов
- Литература
- Правильный способ определения функции предиката в C++
- 5 ответов
Модульный способ вызова функции предиката
Объекты-функции – это объекты, у которых перегружен оператор вызова функций operator(). В библиотеке STL уже определено несколько полезных арифметических и других объектов-функций (описание в файле ):
- plus сложение
- minus вычитание
- multipies умножение
- divides деление
- modulus деление по модулю
- negate отрицание
Рассмотрим пример с отрицанием всех элементов вектора. Можно выполнить этот пример с помощью цикла, а можно сделать намного проще с использованием алгоритма transform и стандартной функции negate.
Алгоритмы (описание в файле ) позволяют выполнять некоторые типовые действия надо контейнерами с использованием объектов-функций стандартной библиотеки или своих объектов-функций. Подробно алгоритмы, функции, и другие возможности библиотеки STL приводятся в Приложении 5.
Стандартные алгоритмы можно использовать и для ввода и вывод контейнера на экран. При чтении ввод происходит до ввода первого не числового символа.
Программисты могут определить свои объекты-функции, которые могут быть наследниками от стандартных. В объектах-функциях обязательно должен быть перегружен оператор вызова функции (), в конструкторе могут задаваться необходимые параметры, или он может быть пустым (см.пример 6.5).
Функции могут быть двух типов:
- Унарная функция – это функция, в которой участвует один операнд (например, x=-y — унарный минуc)
- Бинарная функция – это функция, в которой участвуют два операнда (например, x=y+z — сложение, умножение, и т.д.)
Для унарных функций перегруженный оператор вызова функции должен содержать один параметр, в бинарных – два параметра.
Иногда нужно преобразовать бинарную функцию в унарную, например умножение – бинарная функция, нужны два элемента, а мы хотим умножить все элементы контейнера на одно и то же число. Для этого можно использовать функцию bind2nd.
Функция binder2nd – преобразует бинарную функцию в унарную, и принимает второй аргумент как параметр бинарной функции (описание в файле )
Пример 6.5. Использование объектов-функций
6.4.2. Предикаты. Пример 6.6 (использование предикатов)
Предикаты позволяют без изменения шаблона изменять критерии сравнения элементов контейнера и другие подобные действия. У предикатов объект-функция возвращает значение bool.
В файле уже определено несколько полезных предикатов:
- equal_to бинарный предикат равенства
- not_equal_to бинарный предикат неравенства
- greater бинарный предикат >
- less бинарный предикат =
- less_equal бинарный предикат
Источник
Функторы, предикаты, функциональные адаптеры, лямбда-функции
категория C++ | |
дата | 05.03.2013 |
автор | mrgluck |
голосов | 15 |
Вступление
Статья ориентирована на программистов С++, поверхностно знающих/желающих узнать STL, в особенности, с использованием его алгоритмов. Это краткий обзор по основным понятиям, в конце будет приведен список литературы для более полного ознакомления с материалом.
Часто, алгоритмы STL имеют перегруженную версию или схожую по функционалу с добавлением в названии _if в конце, реализующуюся с применением функционального объекта или функции.
Пример:
copy — copy_if
find — find_if
equal
includes
sort
accumulate
и т.д.
Это позволяет гибко подстраивать их в рамках определенной задачи.
Функторы
Функторы (их еще называют объект-функциями) — конструкция, которая предоставляет возможность использовать объект как функцию. Это может быть структура или класс, перегружающие оператор(). В языке С используется указатель на функцию. Конечно, подобная вещь оставлена для совместимости, но в реальности теряет смысл в использовании в С++, впрочем, не упомянуть о возможности было бы неверно.
Пример использования функтора:
Данный оператор принимает две const строки по ссылке и возвращает истину если длина первой меньше длины второй. Аналогично можно было бы сделать с использованием класса при указании модификатора доступа public для operator().
Часто, функциональные объекты делают шаблонными для лучшей возможности повторного использования кода.
Заметьте, что т.к. тип T неизвестен, то указываем передачу на всякий случай по ссылке, чтобы не создавать временных объектов. При использовании шаблонного объекта обязательно явно объявлять тип в угловых скобках. Необходимо указывать в качестве аргумента название класса, выступающего объект-функцией с круглыми скобками. Вызов может происходить вот так:
const std:: size_t N = 3 ;
int A[N] = < 3 , 2 , 5 >;
std::cout 1 , Mult ());
Надо отметить, что в общем случае, идентификатор типа возвращаемого значения operator() у функтора может быть любой. В данной программе вызывается перегруженная версия алгоритма accumulate (определена в ), которая требует возврата того же типа данных, что и при передаче в функциональный объект, но в других алгоритмах может потребоваться и другой тип данных.
Предикаты
Предикаты- подмножество функторов, в которых тип возвращаемого значения operator() bool. Предикаты используются в алгоритмах сортировок, поиска, а также во всех остальных, имеющих на конце _if. Смысл в том, что объект-функция в случае использования предиката возвращает истину или ложь в зависимости от выполнения необходимого условия. Это либо удовлетворение объектом неких свойств, либо результат сравнения двух объектов по определенному признаку.
Пример использования предиката:
class DividedByTwo
<
public :
bool operator()( const int x) const
<
return x % 2 == 0 ;
>
>;
int main()
<
const std:: size_t N = 3 ;
int A[N] = < 3 , 2 , 5 >;
std::cout
>
Функциональные адаптеры
Основные унарные и бинарные функциональные объекты, необходимые для сравнения, уже включены в STL и используются с добавлением хедера functional. Все они являются шаблонными классами и требуют определения необходимых операторов в типе данных, с которым работают. Примеры: std::greater<>, std::less<>.
Следующий код сортирует элементы по убыванию с применением нашего объекта.
int main()
<
const std:: size_t N = 3 ;
int A[N] = < 3 , 2 , 5 >;
std::sort(A, A + N, std::greater ());
>
Принцип работы std::greater схож со следующим кодом:
Иногда, нам может потребоваться реализовать, например, подсчет элементов исходя из нескольких условий, т.е. необходимо скомбинировать результаты с определенными значениями или другими функциями. В таких случаях применяют функциональные адаптеры. Необходимо отметить, что и сами адаптеры могут служить частью вычислений других адаптеров, за счет чего достигается гибкость вычислений. Основные адаптеры: bind1st, bind2nd, not1, not2.
Пример: подсчитать количество элементов, больших (>), чем два.
int main()
<
const std:: size_t N = 3 ;
int A[N] = < 3 , 2 , 5 >;
std::cout (), 2 ));
>
Данный код выведет 2. Все верно, лишь элементы 3 и 5 превосходят 2.
Пример объединения адаптеров: подсчитать количество элементов, не больших !(>), чем два.
int main()
<
const std:: size_t N = 3 ;
int A[N] = < 3 , 2 , 5 >;
std::cout (), 2 )));
>
Этот ужас, как и ожидалось, выдаст результат 1. Почему ужас? Не знаю, как вам, но по мне, так читаемость этого кода оставляет желать лучшего. И С++11 вводит специальный инструментарий, который позволяет всего этого избежать — лямбда функции, о них мы поговорим далее. Стоит отметить, что и некоторые старые адаптеры и функции были заменены новыми конструкциями — std::function, std::mem_fn, std::bind, а такие, как std::bind1st, std::bind2nd были признаны устаревшими.
Лямбда-функции
Наверное, самая приятная часть 11 стандарта, которая позволяет создавать очень гибкий код прямо на месте, не расползаясь мыслью по разным специальным классам и структурам, создаваемым для сравнения, а также повышающая читаемость кода в разы, ведь критерий сравнения или необходимая для выполнения функция определяется прямо рядом с местом использования. Тем не менее, это не отменяет использование всего того, что было озвучено выше, функциональные объекты имеют право на существование и должны использоваться там, где это действительно принесет полезный результат. Например, если необходимых инструкций для выполнения в теле функции достаточно много и определение данной функции на месте понизит удобочитаемость, или если у нас есть схожие куски кода и мы хотим вынести общий в одно место. В остальных случаях, данная конструкция будет очень удобна. Она обладает множеством различных тонкостей, мы лишь покажем общее с ней ознакомление, т.к. описание всех возможностей требовало бы отдельной статьи. Я покажу лишь использование конструкций с лямбда-функциями, и вы поймете, почему её так полюбили программисты.
Пример: необходимо подсчитать количество неотрицательных элементов, кратных 7
int main()
<
const std:: size_t N = 20 ;
int A[N];
std::iota(A, A + N, — 10 ); // A[0] = -10, A[1] = -9, . A[N-1] = 9
std::cout const int x)
< return x >= 0 && x % 7 == 0 ; > );
>
и есть наша лямбда-функция.
Или же другой: вывести максимальный по модулю элемент
int main()
<
const std:: size_t N = 20 ;
int A[N];
std::iota(A, A + N, — 10 ); // A[0] = -10, A[1] = -9, . A[N-1] = 9
std::cout const int x, const int y)
< return std:: abs(x) std:: abs(y); > );
Необходимо лишь знать список аргументов, передаваемых функции, и то, что она должна делать (список функций вы можете посмотреть по ссылкам на информационные источники ниже в разделе algorithms library). В круглых скобках — список аргументов, которые она принимает (все как и у функтора). В фигурных идет тело функции, выполнение заканчивается после возврата с помощью return, но его наличие вовсе не обязательно.
Например, следующий код значение каждого элемента увеличивает значение на единицу и выводит на экран.
int main()
<
const std:: size_t N = 20 ;
int A[N];
std::iota(A, A + N, — 10 ); // A[0] = -10, A[1] = -9, . A[N-1] = 9
std::for_each(A, A + N, []( int x)
>
Передача аргументов в лямбда-функцию может происходить как по значению, так и по ссылке. Но что делать, если нужно, например, посчитать количество элементов, кратных некоему числу k, которое введет пользователь? Для этого используется список захвата, он передается функции в квадратных скобках, аргументы перечисляются через запятую. Передача может идти также как по значению, так и по ссылке.
Ниже приведен пример, в котором подсчитывается количество элементов, кратных k и больших, чем m.
int main()
<
const std:: size_t N = 20 ;
int A[N], k, m;
std::iota(A, A + N, — 10 ); // A[0] = -10, A[1] = -9, . A[N-1] = 9
std::cin >> k >> m;
std::cout const int x)
< return x % k == 0 && x > m; > );
>
Все просто и лаконично, а главное, читаемость кода возрастает в разы. Теперь я покажу пример, где используется передача аргумента по ссылке.
Пример: подсчитать количество отрицательных элементов и отдельно количество четных, результат вывести на экран. Для простоты, используем алгоритм for_each.
int main()
<
const std:: size_t N = 20 ;
int A[N], num_positives = 0 , num_evens = 0 ;
std::iota(A, A + N, — 10 ); // A[0] = -10, A[1] = -9, . A[N-1] = 9
std::for_each(A, A + N, [&num_positives, &num_evens]( const int x)
<
if (x >= 0 ) num_positives++;
if (x % 2 == 0 ) num_evens++;
> );
std::cout » »
>
Подведение итогов
Я рассмотрел лишь часть функционала, впрочем вышло и так объемно.
Лямбда-функции являются частью языка и не требует подключения дополнительных хедеров. Перед их использованием необходимо убедиться, что ваш компилятор поддерживает 11 стандарт, и установлен ключ -std=c++11 (или -std=c++0x). Также, лямбда-выражения доступны с использованием семейства библиотек boost. VS поддерживает лямбда-функции лишь с 2010 студии, для пользователей gcc и, соотв. mingw рекомендуется обновиться до/на основе версии 4.7.0 и выше.
Команда в Ubuntu для обновления gcc:
Литература
Если Вам понравилась статья, проголосуйте за нее
Голосов: 15 Голосовать
Источник
Правильный способ определения функции предиката в C++
Я пытаюсь написать функцию предиката для использования с алгоритмами STL. Я вижу, что это два способа определить предикат:
(1) использовать простую функцию, как показано ниже:
(2) Используйте функцию operator (), как показано ниже:
Я больше использую для второго типа, поскольку я обычно хотел бы создать объект предиката с некоторыми членами в нем и использовать их в алгоритме. Когда я добавляю ту же функцию isEven внутри checker и использую ее как сказуемое, я получаю ошибку:
3. Синтаксис, который дает ошибку:
вызов c.isEven дает ошибку во время компиляции, говоря неопределенную ссылку на некоторую функцию. Может кто-нибудь объяснить почему 3. дает ошибку? Кроме того, я был бы признателен за любые указатели, чтобы прочитать об основах предикатов и итераторов.
5 ответов
я думаю, это потому, что типа c.isEven() есть
чего нельзя ожидать от find_if() . std::find_if следует ожидать либо указатель на функцию ( bool (*)(unsigned int) ) или объект функции.
редактировать: другое ограничение: не — static указатель функции-члена должен вызываться class объект. В вашем случае, даже если вам удастся передать функцию-член, затем еще find_if() не будет иметь никакой информации о каких-либо checker объект; поэтому нет смысла иметь find_if() перегружен для принятия аргумента указателя функции-члена.
Примечание: в общем c.isEven не является правильным способом передачи указателя функции-члена; он должен быть передан как, &checker::isEven .
указатель на функцию-член требует, чтобы вызывался экземпляр, и вы передаете только указатель на функцию-член в std::find_if (на самом деле ваш синтаксис неверен, поэтому он вообще не работает; правильный синтаксис std::find_if(itBegin, itEnd, &checker::isEven) который тогда все еще не работает по причинам, которые я дал).
на find_if функция ожидает, что сможет вызвать функцию с помощью одного параметра (объект для тестирования), но на самом деле для вызова функции-члена требуется два: экземпляр this указатель и объект для сравнения.
перегрузка operator() позволяет передавать как экземпляр, так и объект функции одновременно, потому что теперь это одно и то же. С помощью указателя функции-члена Вы должны передать две части информации функции, которая ожидает только одну.
есть способ сделать это с помощью std::bind (которая требует ):
если ваш компилятор не поддерживает std::bind , вы также можете использовать boost::bind для этот. Хотя нет никакого реального преимущества в том, чтобы делать это просто перегружая operator() .
чтобы уточнить немного больше, std::find_if ожидает указатель функции, соответствующий сигнатуре bool (*pred)(unsigned int) или что-то, что ведет себя таким образом. На самом деле это не должен быть указатель функции, потому что тип предиката связан шаблоном. Все, что ведет себя как bool (*pred)(unsigned int) приемлемо, поэтому функторы работают: их можно вызвать с одним параметром и вернуть bool .
как указывали другие, тип checker::isEven is bool (checker::*pred)(unsigned int) который не ведет себя как исходный указатель функции, потому что ему нужен экземпляр checker для вызова.
указатель на функцию-член можно концептуально рассматривать как указатель регулярной функции, который принимает дополнительный аргумент, this указатель (например, bool (*pred)(checker*, unsigned int) ). На самом деле вы можете создать оболочку, которая может быть вызвана таким образом, используя std::mem_fn(&checker::isEven) (также с ). Это все равно не поможет вам, потому что теперь у вас есть объект функции, который должен вызываться с двумя параметрами, а не только с одним, который std::find_if все еще не нравится.
используя std::bind лечит указатель на функцию-член, как если бы это была функция приема this указатель в качестве первого аргумента. Аргументы перешли к std::bind указывают, что первый аргумент всегда должен быть &c , а второй аргумент должен привязываться к первому аргументу вновь возвращаемой функции объект. Этот объект функции является оболочкой, которая может вызываться с одним аргументом и поэтому может использоваться с std::find_if .
хотя тип возврата std::bind не указано, Вы можете преобразовать его в std::function (в данном конкретном случае), если вам нужно явно ссылаться на объект связанной функции, а не передавать его прямо другой функции, как я сделал в моем примере.
Источник