Литмир - Электронная Библиотека
A
A

• Ее можно вызывать как предикат, например

pred(*first)
.

• Она может хранить значение, например

31
или
x
, передаваемое при вызове.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Для того чтобы выполнить эти условия, нам нужен объект-функция, т.е. объект, который ведет себя как функция. Нам нужен объект, поскольку именно объекты могут хранить данные, например значение для сравнения. Рассмотрим пример.

class Larger_than {

  int v;

public:

  Larger_than(int vv) : v(vv) { } // хранит аргумент

  bool operator()(int x) const { return x>v; } // сравнение

};

Следует отметить, что это определение представляет собой именно то, что мы требовали от предиката. Теперь осталось понять, как это работает. Написав выражение

Larger_than(31)
, мы (очевидно) создаем объект класса
Larger_than
, хранящий число
31
в члене
v
. Рассмотрим пример.

find_if(v.begin(),v.end(),Larger_than(31))

Здесь мы передаем объект

Larger_than(31)
алгоритму
find_if()
как параметр с именем
pred
. Для каждого элемента v алгоритм
find_if()
осуществляет вызов

pred(*first)

Это активизирует оператор вызова функции, т.е. функцию-член operator(), для объекта-функции с аргументом

*first
. В результате происходит сравнение значения элемента, т.е.
*first
, с числом
31
.

Мы видим, что вызов функции можно рассматривать как результат работы оператора

()
, аналогично любому другому оператору. Оператор
()
называют также оператором вызова функции (function call operator) или прикладным оператором (application operator). Итак, оператор
()
в выражении
pred(*first)
эквивалентен оператору
Larger_than::operator()
, точно так же, как оператор
[]
в выражении
v[i]
эквивалентен оператору
vector::operator[]
.

21.4.1. Абстрактная точка зрения на функции-объекты

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Таким образом, мы имеем механизм, позволяющий функции хранить данные, которые ей нужны. Очевидно, что функции-объекты образуют универсальный, мощный и удобный механизм. Рассмотрим понятие объекта-функции подробнее.

class F {  // абстрактный пример объекта-функции

      S s; // состояние

public:

  F(const S& ss):s(ss) { /* устанавливает начальное значение */ }

  T operator() (const S& ss) const

  {

    // делает что-то с аргументом ss

    // возвращает значение типа T (часто T — это void,

    // bool или S)

  }

  const S& state() const { return s; } // демонстрирует

  // состояние

  void reset(const S& ss) { s = ss; }  // восстанавливает

  // состояние

};

Объект класса

F
хранит данные в своем члене
s
. По мере необходимости объект-функция может иметь много данных-членов. Иногда вместо фразы “что-то хранит данные” говорят “нечто пребывает в состоянии”. Когда мы создаем объект класса
F
, мы можем инициализировать это состояние. При необходимости мы можем прочитать это состояние. В классе
F
для считывания состояния предусмотрена операция
state()
, а для записи состояния — операция
reset()
. Однако при разработке объекта-функции мы свободны в выборе способа доступа к его состоянию.

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

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Использование объектов-функций является основным способом параметризации в библиотеке STL. Мы используем объекты-функции для того, чтобы указать алгоритму поиска, что именно мы ищем (см. раздел 21.3), для определения критериев сортировки (раздел 21.4.2), для указания арифметических операций в численных алгоритмах (раздел 21.5), для того, чтобы указать, какие объекты мы считаем равными (раздел 21.8), а также для многого другого. Использование объектов-функций — основной источник гибкости и универсальности алгоритмов.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Объекты-функции, как правило, очень эффективны. В частности, передача по значению небольшого объекта-функции в качестве аргумента шаблонной функции обеспечивает оптимальную производительность. Причина проста, но удивительна для людей, хорошо знающих механизм передачи функций в качестве аргументов: обычно передача функции в виде объекта приводит к созданию значительно более маленького и быстродействующего кода, чем при передаче функции как таковой! Это утверждение оказывается истинным, только если объект мал (например, если он содержит одно-два слова данных или вообще не хранит данные) или передается по ссылке, а также если оператор вызова функции невелик (например, простое сравнение с помощью оператора
<
) и определен как подставляемая функция (например, если его определение содержится в теле класса). Большинство примеров в этой главе — и в книге в целом — соответствует этому правилу. Основная причина высокой производительности небольших и простых объектов-функций состоит в том, что они предоставляют компилятору объем информации о типе, достаточный для того, чтобы сгенерировать оптимальный код. Даже устаревшие компиляторы с несложными оптимизаторами могут генерировать простую машинную инструкцию “больше” для сравнения в классе
Larger_than
, вместо вызова функции. Вызов функции обычно выполняется в 10–50 раз дольше, чем простая операция сравнения. Кроме того, код для вызова функции больше, чем код простого сравнения.

289
{"b":"847443","o":1}