Перепишем функцию определения нечетности как класс-функтор (листинг 12.9).
Листинг 12.9. Класс-функтор Odd
class Odd { public:
int operator()(const int& d)
{ return (d%2): }
};
Как видите, по сравнению с функцией изменения выглядят чисто косметическими: имя функции заменено на operatorO, и сама функция «обернута» в класс, как в листинге 12.8. Однако последствия таких минимальных изменений колоссальны! Так как мы имеем класс, то можем использовать разнообразнейшие возможности объектно-ориентированного подхода, например:
• реализовать в классе любые необходимые методы и операции, в том числе перегрузить операцию operator() несколько раз;
• объявить в классе любые поля для сохранения состояния функтора (в разные моменты времени функтор может иметь разные состояния, следовательно, перед использованием функтор можно инициализировать);
• если необходимо, то можно построить иерархию функторов, в том числе и с виртуальными функциями — операция operatorO тоже может быть сделана виртуальной.
Функторы обладают еще одной важной особенностью по сравнению с указателями на функции. Тип обычной функции (и указателя на функцию) определяется только прототипом. Если прототипы одинаковы, то и тип один и тот же. Объек^ ты функций могут быть разного типа даже при идентичных прототипах операции operatorO — просто назовите классы по-разному. Это открывает возможности унифицированного программирования с применением шаблонов, так как позволяет передавать поведение как параметр шаблона. Именно эта особенность позволяет реализовать паттерн Strategy (стратегия) на этапе компиляции [17].
Синтаксис вызова функтора такой же, как и обычной функции, только вместо имени функции задается имя класса-функтора. Пусть у нас объявлен объект-функция:
Odd f;
Тогда вызов записывается так: f(5);
Это сокращенная запись, полная выглядит более громоздко: f.operator()(5);
Наша обобщенная функция-фильтр copy_if () с вызовом функтора показана в листинге 12.10.
Листинг 12.10. Вызов функтора в copy_if()
template < class Inputlterator, class Outputlterator, class Predicate
>
void copy__if( Inputlterator first, Inputlterator last, Outputlterator result, Predicate Functor
)
{ for ( ; first != last; ++first)
if (Functor(*first)) // вызов функтора
{ *result = *first; ++result; } return;
}
// проверка работы функтора int mainQ
{ int a[10] = { 1,2.3.4,5,6,7.8,9,0}; int b[19] ={9}; copy_if(a, a+10, b, Odd()); for(int i =0; i < 10; ++1)
cout << b[i] << ' '; cout << endl;
}
Как видим, определение функции-фильтра с точностью до имен совпадает с определением в листинге 12.7. Вызов функтора в теле функции copy_if () совпадает с вызовом функции в листинге 12.7:
Functor(*fi rst)
Аргумент-функтор в списке передаваемых параметров задан в виде Odd (). Это — просто вызов конструктора и создание анонимного объекта-функции типа Odd. Можно объект создать явно и передать его в copy_i f (), например:
Odd f;
copy_if(a, a+10, b, f);
Результат — тот же.
Обратите внимание на то, что в листингах 12.7 и 12.10 функции-фильтры copy_if () абсолютно идентичны. С практической точки зрения это означает, что мы можем передавать в качестве аргумента-предиката, как и раньше, обычную функцию! Например, мы получим те же самые результаты, использовав приведенную ранее функцию-предикат odd ():
copy_if(a, a+10, b, odd);
Таким образом, мы еще более обобщили нашу функцию-фильтр.,
Первым приятным следствием такого обобщения является то, что теперь можно задавать предикаты с несколькими параметрами — просто в классе задается нужное количество полей соответствующих типов. Функтор, имеющий поля, называют функтором с состоянием. Заполнение этих полей выполняет конструктор, который может выполнять сколь угодно сложную обработку. Напишем класс-функтор, обеспечивающий сравнение элемента контейнера с любым значением на «больше» (листинг 12.11). |