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.
Определите объект-функцию для возврата среднего из трех объектов. Определите функцию для выполнения той же операции. Приведите примеры использования каждого объекта непосредственно и путем передачи его функции. Покажите, в чем сходство и различие этих решений.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
- 364.
- 365.
- 366.
- 367.
- 368.
- 369.
- 370.
- 371.
- 372.
- 373.
- 374.
- 375.
- 376.
- 377.
- 378.
- 379.
- 380.
- 381.
- 382.
- 383.
- 384.
- 385.
- 386.
- 387.
- 388.
- 389.
- 390.
- 391.
- 392.
- 393.
- 394.
- 395.
- 396.
- 397.
- 398.
- 399.
- 400.
- 401.
- 402.
- 403.
- 404.
- 405.
- 406.
- 407.
- 408.
- 409.
- 410.
- 411.
- 412.
- 413.
- 414.
- 415.
- 416.
- 417.
- 418.
- 419.
- 420.
- 421.
- 422.
- 423.
- 424.
- 425.
- 426.
- 427.
- 428.
- 429.
- 430.
- 431.
- 432.
- 433.
- 434.
- 435.
- 436.
- 437.
- 438.
- 439.
- 440.
- 441.
- 442.
- 443.
- 444.
- 445.
- 446.
- 447.
- 448.
- 449.
- 450.
- 451.
- 452.
- 453.
- 454.
- 455.
- 456.
- 457.
- 458.
- 459.
- 460.
- 461.
- 462.
- 463.
- 464.
- 465.
- 466.
- 467.
- 468.
- 469.
- 470.