Шаг 116.
Библиотека STL.
Объекты функций. Понятие объекта функции (окончание)

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

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

void add10 (int& elem) {
  elem += 10; 
}

void f1()
{
  vector<int> coll;
  .   .   .   .
  for_each(coll.begin(), coll.end(), // Интервал
    add10);                          // Операция
}

    Если возможны разные приращения, известные па стадии компиляции, функция оформляется в виде шаблона:

template <int theValue> void add (int& elem) {
  elem += theValue; 
}

void f1()
{
  vector<int> coll;
  .   .   .   .
  for_each (coll .begin(), coll.end(), // Интервал
    add<10>);                          // Операция
}

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

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

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

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

#include <vcl.h>
#include <iostream>
#include <list>
#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;
}

template <class T>
inline void PRINT_ELEMENTS (const T& coll, const char* optcstr="")
{
  typename T::const_iterator pos;
  std::cout << ToRus(optcstr);
  for (pos=coll.begin(); pos!=coll.end(); ++pos) {
    std::cout <<*pos <<' ';
  }
  std::cout << std::endl;
}

// Объект функции прибавляет к значению элемента приращение,
// заданное при его инициализации
class AddValue {
  private:
    int theValue;     // Приращение
  public:
    // Конструктор инициализирует приращение
    AddValue(int v) : theValue(v) {}
    // Суммирование выполняется "вызовом функции" для элемента
    void operator() (int& elem) const {
      elem += theValue;
    }
};

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

  PRINT_ELEMENTS (coll,"Инициализация:\n");
  // Прибавить к каждому элементу 10
  for_each (coll.begin(),coll.end(), // Интервал
    AddValue(10)); // Операция

  PRINT_ELEMENTS (coll,"После добавления 10:\n");

  // Прибавить к каждому элементу значение первого элемента
  for_each (coll.begin(), coll.end(),     // Интервал AddValue
    AddValue(*coll.begin()) );     // Операция

  PRINT_ELEMENTS(coll,"После добавления первого элемента:\n");

  cout << endl;

  getch();
  return 0;
}
//---------------------------------------------------------------------------
Текст этого примера можно взять здесь.

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


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

    После инициализации коллекция содержит числа от 1 до 9:

Инициализация:
1 2 3 4 5 6 7 8 9

    Первый вызов for_each() увеличивает каждый элемент на 10:

for_each (coll .begin(), coll.end(), // Интервал
  AddValue(10));                     // Операция

    Выражение AddValue(10) создает объект типа AddValue и инициализирует его значением 10. Конструктор AddValue сохраняет это значение в переменной theValue. Во время работы for_each() для каждого элемента coll вызывается оператор (). Еще раз стоит уточнить, что речь идет о вызове оператора () для переданного временного объекта функции типа AddValue. Сам элемент передается в аргументе. Объект функции увеличивает каждый элемент на 10. В результате содержимое коллекции выглядит так:

После добавления 10:
11 12 13 14 15 16 17 18 19

    Второй вызов for_each() тем же способом прибавляет значение первого элемента к каждому элементу коллекции. При этом временный объект функции AddValue инициализируется первым элементом коллекции: AddValue(*coll.begin()) На этот раз результат выглядит так:

После добавления первого элемента:
22 23 24 25 26 27 28 29 30

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

AddValue addx(x);         // Объект функции, прибавляющий значение х 
AddValue addx(y);         // Обьект функции, прибавляющий значение у
for_each (coll .begin(), coll.end(),  // Прибавление значения х
  addx);                              // к каждому элементу
.    .    .    .
for_each (coll .begin(), coll.end(),  // Прибавление значения у
  addy);                              // к каждому элементу
.    .    .    .
for_each (coll .begin(), coll.end(),  // Прибавление значения х
  addx);                              // к каждому элементу

    Аналогичным способом определяются другие функции для получения или изменения состояния объекта функции на протяжении его жизненного цикла.

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

    На следующем шаге мы рассмотрим стандартные объекты функций.




Предыдущий шаг Содержание Следующий шаг