Объекты функций. Понятие объекта функции

   
На этом шаге мы рассмотрим основные принципы использования функций в качестве объектов.

   
Функциональные аргументы, передаваемые алгоритмам, не обязаны быть функциями. Они могут быть объектами,
которые ведут себя как функции. Такие объекты называются объектами функций, или функторами.
Иногда объект функции справляется с ситуацией, в которой обычная функция не работает. Объекты функций часто
используются в STL, причем некоторые являются очень полезными.
Понятие объекта функции

   
Объекты функций - еще один пример унифицированного программирования и концепции чистой
абстракции. В соответствии с этой концепцией все, что ведет себя как функция, является функцией. Следовательно, если
определить объект, который ведет себя как функция, он может использоваться в качестве функции. Но как понимать
фразу "ведет себя как функция"? Ответ: это означает возможность вызова с круглыми скобками и передачей аргументов.
Пример:

  function(arg1,arg2);  // Вызов функции

   
Следовательно, если мы хотим, чтобы объект вел себя подобным образом, необходимо обеспечить возможность его "вызова"
в программе с круглыми скобками и передачей аргументов. Да, такое вполне возможно (в C++ вообще редко
встречается что-нибудь невозможное). От вас лишь потребуется определить оператор () с соответствующими типами параметров:

class X {
  public:
  // Определение оператора "вызов функции" 
  возвращаемое_значение operator() (аргументы) const;
  .   .   .   .
};

   
Теперь объекты этого класса ведут себя как функции и могут вызываться в программе:

  X fo;
  .   .   .   .
  fo(arg1,arg2);       // Вызов оператора () для объекта функции fo 

   
Такой вызов эквивалентен следующему:

  fo.operator()(arg1,arg2);  // Вызов оператора () для объекта функции fo

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

//---------------------------------------------------------------------------

#include <vcl.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <conio.h> //необходимо для getch()

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;

std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

// Простой объект функции для вывода передаваемого аргумента
class PrintInt {
  public:
    void operator() (int elem) const {
      cout << elem << ' ';
    }
};

int main(int argc, char* argv[])
{
  vector<int> coll;
  // Вставка элементов со значениями от 1 до 9
  for (int i=1; i<=9; ++i) {
    coll.push_back(i);
  }

  // Вывод всех элементов
  cout << ToRus("Элементы вектора:\n");
  for_each (coll.begin(),coll.end(), // Интервал
    PrintInt());                     // Операция
  cout << endl;

  getch();
  return 0;
}
//---------------------------------------------------------------------------

Текст этого примера можно взять здесь.

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


Рис.1. Результат работы приложения

   
Класс PrintInt определяет объект, для которого вызывается оператор () с аргументом int. Выражение
PrintInt() в следующей команде создает временный объект этого класса, передаваемый алгоритму for_each() в качестве аргумента:

  for_each (coll .begin(), coll.end(), 
    PrintInt());

   
Алгоритм for_each() определяется примерно так:

namespace std {
  template <class Iterator, class Operation>
  Operation for_each (Iterator act, Iterator end, Operation op)
  {
    while (act != end) {  // Пока не достигнут конец интервала
      op(*act);           // - вызвать ор() для текущего элемента
      ++act;              // - переместить итератор к следующему
    }                     // элементу.
    return op;
  }
}

   
Алгоритм for_each() использует временный объект функции ор, чтобы для каждого элемента делать
вызов op(*act). Если третий параметр является обычной функцией, она просто вызывается с аргументом *act.
Если третий параметр является объектом функции, вызывается оператор () объекта функции ор с аргументом *act.
Таким образом, в приведенной программе алгоритм for_each() вызывает операторную функцию

  PrintInt::operator()(*act)

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

  • Объект функции - это "умная функция". Объекты, которые ведут себя как указатели, называются "умными
    указателями". По аналогии объекты, которые ведут себя как функции, можно назвать "умными функциями", поскольку
    их возможности не ограничиваются вызовом оператора (). Объекты функций могут представлять другие функции и
    иметь другие атрибуты, а это означает, что объекты функций обладают состоянием. Одна и та же функция,
    представленная объектом функции, в разные моменты времени может находиться в разных состояниях. У обычных
    функций такая возможность отсутствует. Из этого следует одно из достоинств объектов функций - возможность
    их инициализации на стадии выполнения перед вызовом/использованием.
  • Каждому объекту функции соответствует свой тип. Обычные функции обладают разными типами только при
    различиях в их сигнатурах. С другой стороны, объекты функций могут иметь разные типы даже при полном
    совпадении сигнатур. Это открывает новые возможности для унифицированного программирования с применением
    шаблонов, потому что функциональное поведение может передаваться как параметр шаблона. Например, это
    позволяет контейнерам разных типов использовать единственный объект функции в качестве критерия сортировки.
    Тем самым предотвращается возможное присваивание, слияние и сравнение коллекций, использующих разные критерии
    сортировки. Вы даже можете спроектировать иерархию объектов функций, например, для определения нескольких
    специализированных версий одного общего критерия.
  • Объекты функций обычно работают быстрее функций. Шаблоны обычно лучше оптимизируются, поскольку на
    стадии компиляции доступно больше информации. Следовательно, передача объекта функции вместо обычной
    функции часто повышает быстродействие программы.

   
На следующем шаге мы закончим изучение этого вопроса.



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

Оставить комментарий