C++ для начинающих.

12.3. Объекты-функции.

Наша функция min() дает хороший пример как возможностей, так и ограничений механизма шаблонов:

Template typename Type.

Const 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;

}

Достоинство этого механизма – возможность определить единственный шаблон min(), который конкретизируется для бесконечного множества типов. Ограничение же заключается в том, что даже при такой конкретизации min() будет работать не со всеми.

Это ограничение вызвано использованием оператора "меньше": в некоторых случаях базовый тип его не поддерживает. Так, класс изображения Image может и не предоставлять реализации такого оператора, но мы об этом не знаем и пытаемся найти минимальный кадр анимации в данном массиве изображений. Однако попытка конкретизировать min() для такого массива приведет к ошибке компиляции:

Error: invalid types applied to the.

Возможна и другая ситуация: оператор "меньше" существует, но имеет неподходящую.

Семантику. Например, если мы хотим найти наименьшую строку, но при этом принимать во.

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

Не даст нужного результата.

Традиционное решение состоит в том, чтобы параметризовать оператор сравнения. В данном.

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

Возвращающую значение типа bool:

Template typename Type,

Bool (*Comp)(const Type&, const Type&)&

Const Type&

Min( const Type *p, int size, Comp comp ).

{

Type minval = p[ 0 ];

For ( int ix = 1; ix size; ++ix ).

If ( Comp( p[ ix ] minval )).

Minval = p[ ix ];

Return minval;

}

Такое решение вместе с нашей первой реализацией на основе встроенного оператора "меньше" обеспечивает универсальную поддержку для любого типа, включая и класс Image, если только мы придумаем подходящую семантику для сравнения двух изображений. Основной недостаток указателя на функцию связан с низкой эффективностью, так как косвенный вызов не дает воспользоваться преимуществами встроенных функций.

Альтернативная стратегия параметризации заключается в применении объекта-функции вместо указателя (примеры мы видели в предыдущем разделе). Объект-функция – это класс, перегружающий оператор вызова (operator()). Такой оператор инкапсулирует семантику обычного вызова функции. Объект-функция, как правило, передается обобщенному алгоритму в качестве аргумента, хотя можно определять и независимые объекты-функции. Например, если бы был определен объект-функция AddImages, который принимает два изображения, объединяет их некоторым образом и возвращает новое изображение, то мы могли бы объявить его следующим образом:

AddImages AI;

Чтобы объект-функция удовлетворял нашим требованиям, мы применяем оператор вызова, предоставляя необходимые операнды в виде объектов класса Image:

Image im1("foreground.tiff"), im2("background.tiff");

// ...

// вызывает Image AddImages::operator()(const Image1&, const Image2&);

Image new_image = AI (im1, im2 );

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

Ниже приведена измененная реализация шаблона min() (отметим, что это объявление допускает также и передачу указателя на функцию, но без проверки прототипа):

Template typename Type,

Typename Comp.

Const Type&

Min( const Type *p, int size, Comp comp ).

{

Type minval = p[ 0 ];

For ( int ix = 1; ix size; ++ix ).

If ( Comp( p[ ix ] minval )).

Minval = p[ ix ];

Return minval;

}

Как правило, обобщенные алгоритмы поддерживают обе формы применения операции: как использование встроенного (или перегруженного) оператора, так и применение указателя на функцию либо объекта-функции.

Есть три источника появления объектов-функций:

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

12.3.1. Предопределенные объекты-функции.

Предопределенные объекты-функции подразделяются на арифметические, логические и сравнительные. Каждый объект – это шаблон класса, параметризованный типами операндов. Для использования любого из них необходимо включить заголовочный файл:

#include functional.

Например, объект-функция, поддерживающий сложение, – это шаблон класса с именем plus. Для определения экземпляра, способного складывать два целых числа, нужно.

#include functional.

Написать:

Plus int intAdd;

Для выполнения операции сложения мы применяем перегруженный оператор вызова к intAdd точно так же, как и к классу AddImage в предыдущем разделе:

Int ival1 = 10, ival2 = 20;

// эквивалентно int sum = ival1 + ival2;

Int sum = intAdd( ival1, ival2 );

Реализация шаблона класса plus вызывает оператор сложения, ассоциированный с типом своего параметра – int. Этот и другие предопределенные объекты-функции применяются прежде всего в качестве аргументов обобщенных алгоритмов и обычно замещают подразумеваемую по умолчанию операцию. Например, по умолчанию алгоритм sort() располагает элементы контейнера в порядке возрастания с помощью оператора "меньше" базового типа. Для сортировки по убыванию мы передаем.

Vector string svec;

// ...

Предопределенный шаблон класса greater, который вызывает оператор "больше":

Sort( svec.begin(), svec.end(), greaterstring() );

Предопределенные объекты-функции перечислены в следующих разделах и разбиты на категории: арифметические, логические и сравнительные. Применение каждого из них иллюстрируется как в качестве именованного, так и в качестве безымянного объекта, передаваемого функции. Мы пользуемся следующими определениями объектов, включая и определение простого класса (перегрузка операторов подробно рассматривается в главе.

Class Int {

Public:

Int( int ival = 0 ) : _val( ival ) {}.

Int operator-() { return -_val; }.

Int operator%(int ival) { return -_val % ival; }.

Bool operator(int ival) { return -_val ival; }.

Bool operator!() { return -_val == 0; }.

Private:

Int _val;

};

Vector string svec;

String sval1, sval2, sres;

Complex cval1, cval2, cres;

Int ival1, ival2, ires;

Int Ival1, Ival2, Ires;

15):

Double dval1, dval2, dres;

Кроме того, мы определяем два шаблона функций, которым передаем различные безымянные объекты-функции:

Template class FuncObject, class Type.

Type UnaryFunc( FuncObject fob, const Type &val ).

{ return fob( val ); }.

Template class FuncObject, class Type.

Type BinaryFunc( FuncObject fob,

Const Type &val1, const Type &val2 ).

{ return fob( val1, val2 ); }.

12.3.2. Арифметические объекты-функции.

Предопределенные арифметические объекты-функции поддерживают операции сложения, вычитания, умножения, деления, взятия остатка и вычисления противоположного по знаку значения. Вызываемый оператор – это экземпляр, ассоциированный с типом Type. Если тип является классом, предоставляющим перегруженную реализацию оператора, то именно эта реализация и вызывается.

Plusstring stringAdd;

// вызывается string::operator+().

Sres = stringAdd( sval1, sval2 );

Сложение:

PlusType.

*

Dres = BinaryFunc( plusdouble(), dval1, dval2 );

Minusint intSub;

Ires = intSub( ival1, ival2 );

Вычитание:

MinusType.

*

Dres = BinaryFunc( minusdouble(), dval1, dval2 );

Multipliescomplex complexMultiplies;

Cres = complexMultiplies( cval1, cval2 );

Умножение:

MultipliesType.

*

Dres = BinaryFunc( multipliesdouble(), dval1, dval2 );dividesint intDivides;

Ires = intDivides( ival1, ival2 );

Деление:

DividesType.

Dres = BinaryFunc( dividesdouble(), dval1, dval2 );

Взятие остатка:

ModulusType.

*

ModulusInt IntModulus;

Ires = IntModulus( Ival1, Ival2 );

Ires = BinaryFunc( modulusint(), ival1, ival2 );

Negateint intNegate;

Ires = intNegate( ires );

Вычисление противоположного значения:

NegateType.

*

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 complexNotEqual;

Cres = complexNotEqual( cval1, cval2 );

Ires = count_if( svec.begin(), svec.end(),

Неравенство:

Not_equal_toType.

*

Not_equal_tostring(), sval1 );

Greaterint intGreater;

Ires = intGreater( ival1, ival2 );

Ires = count_if( svec.begin(), svec.end(),

Больше:

GreaterType.

*

Greaterstring(), sval1 );

Greater_equaldouble doubleGreaterEqual;

Dres = doubleGreaterEqual( dval1, dval2 );

Ires = count_if( svec.begin(), svec.end(),

Больше или равно:

Greater_equalType.

*

Greater_equal string(), sval1 );

LessInt IntLess;

Ires = IntLess( Ival1, Ival2 );

Ires = count_if( svec.begin(), svec.end(),

Меньше:

LessType.

*

Lessstring(), sval1 );

Less_equalint intLessEqual;

Ires = intLessEqual( ival1, ival2 );

Ires = count_if( svec.begin(), svec.end(),

Меньше или равно:

Less_equalType.

*

Less_equalstring(), sval1 );

12.3.4. Логические объекты-функции.

Логические объекты-функции поддерживают операции "логическое И" (возвращает true, если оба операнда равны true, – применяет оператор &&, ассоциированный с типом Type), "логическое ИЛИ" (возвращает true, если хотя бы один из операндов равен true, – применяет оператор ||, ассоциированный с типом Type) и "логическое НЕ" (возвращает true, если операнд равен false, – применяет оператор !, ассоциированный с типом Type).

Logical_andint intAnd;

Ires = intLess( ival1, ival2 );

Логическое И:

Logical_andType.

*

Dres = BinaryFunc( logical_anddouble(), dval1, dval2 );

Logical_orint intSub;

Ires = intSub( ival1, ival2 );

Логическое ИЛИ:

Logical_orType.

*

Dres = BinaryFunc( logical_ordouble(), dval1, dval2 );

Logical_notInt IntNot;ires = IntNot( Ival1, Ival2 );

Логическое НЕ:

Logical_notType.

*

Dres = UnaryFunc( logical_ordouble(), dval1 );

12.3.5. Адаптеры функций для объектов-функций.

В стандартной библиотеке имеется также ряд адаптеров функций, предназначенных для специализации и расширения как унарных, так и бинарных объектов-функций. Адаптеры – это специальные классы, разбитые на следующие две категории:

* связыватели (binders). Это адаптеры, преобразующие бинарный объект-функцию в унарный объект, связывая один из аргументов с конкретным значением. Например, для подсчета в контейнере всех элементов, которые меньше или равны 10, следует передать алгоритму count_if() объект-функцию less_equal, один из аргументов которого равен 10. В следующем разделе мы покажем, как это сделать;

* отрицатели (negators). Это адаптеры, изменяющие значение истинности объекта-функции на противоположное. Например, для подсчета всех элементов внутри контейнера, которые больше 10, мы могли бы передать алгоритму count_if() отрицатель объекта-функции less_equal, один из аргументов которого равен 10. Конечно, в данном случае проще передать связыватель объекта-функции greater, ограничив один из аргументов со значением 10.

В стандартную библиотеку входит два предопределенных адаптера-связывателя: bind1st и bind2nd, причем bind1st связывает некоторое значение с первым аргументом бинарного объекта-функции, а bind2nd – со вторым. Например, для подсчета внутри контейнера всех элементов, которые меньше или равны 10, мы могли бы передать.

Count_if( vec.begin(), vec.end(),

Алгоритму count_if() следующее:

Bind2nd( less_equalint(), 10 ));

В стандартной библиотеке также есть два предопределенных адаптера-отрицателя: not1 и not2. not1 инвертирует значение истинности унарного предиката, являющегося объектом-функцией, а not2 – значение бинарного предиката. Для отрицания рассмотренного ыше связывателя объекта-функции less_equal можно написать.

Count_if( vec.begin(), vec.end(),

Следующее:

Not1( bind2nd( less_equalint(), 10 )));

Другие примеры использования связывателей и отрицателей приведены в Приложении, вместе с примерами использования каждого алгоритма.

12.3.6. Реализация объекта-функции.

При реализации программы в разделе 12.2 нам уже приходилось определять ряд объектов-функций. В этом разделе мы изучим необходимые шаги и возможные вариации при определении класса объекта-функции. (В главе 13 определение класса рассматривается детально; в главе 15 обсуждается перегрузка операторов.).В самой простой форме определение класса объекта-функции сводится к перегрузке оператора вызова. Вот, например, унарный объект-функция, определяющий, что.

// простейшая форма класса объекта-функции.

Class less_equal_ten {

Public:

Bool operator() ( int val ).

{ return val = 10; }.

Некоторое значение меньше или равно 10:

};

Теперь такой объект-функцию можно использовать точно так же, как предопределенный. Вызов алгоритма count_if() с помощью нашего объекта-функции выглядит следующим образом:

Count_if( vec.begin(), vec.end(), less_equal_ten() );

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

Count_if( vec.begin(), vec.end(),

Отрицатель, чтобы подсчитать, сколько в контейнере элементов, больших 10:

Not1(less_equal_then ()));

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

Class less_equal_value {

Public:

Less_equal_value( int val ) : _val( val ) {}.

Bool operator() ( int val ) { return val = _val; }.

Private:

Int _val;

Указанной пользователем величиной:

};

Новый объект-функция применяется для задания произвольного целого значения. Например, при следующем вызове подсчитывается число элементов, меньших или равных 25:

Count_if( vec.begin(), vec.end(), less_equal_value( 25 ));

Разрешается реализовать класс и без конструктора, если параметризовать его значением, с которым производится сравнение:

Template int _val.

Class less_equal_value {

Public:

Bool operator() ( int val ) { return val = _val; }.

};

Вот как надо было бы вызвать такой класс для подсчета числа элементов, меньших или равных 25:

Count_if( vec.begin(), vec.end(), less_equal_value25());

(Другие примеры определения собственных объектов-функций можно найти в Приложении.).

Упражнение 12.4.

Используя предопределенные объекты-функции и адаптеры, создайте объекты-функции для решения следующих задач:

(a)Найти все значения, большие или равные 1024.

(b)Найти все строки, не равные "pooh".

(c)Умножить все значения на 2.

Упражнение 12.5.

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