Функторы и лямбда-функции
дата публикации: 2016-10-30

Функциональным объектом (функтором) в языке С++ называется экземпляр класса, у которого перегружен operator(). Дело все в том, что такой объект в общем-то ведет себя как функция, при этом может сохранять внутри результаты вычислений. Такое поведение может быть весьма полезно, когда результатом является совокупность вызовов функции, а не конкретный вызов, конечно, данные можно хранить и вне функции, например, организуя цикл для сортировки, заводится отдельная переменная, однако такое решение не является гибким и не устойчиво к ошибкам. Приведу пример использования функтора:

class functor {
  int sum;
public:
  functor();
  void operator()(int x);
  int get_sum() const;
};

functor::functor() : sum(0) {
}

void functor::operator()(int x) {
  sum += x;
}

int functor::get_sum() const {
  return sum;
}

int main() {
  vector list{1,2,3,4,5};
  functor f;
  f = for_each(list.begin(), list.end(), f);
  cout << f.get_sum() << endl;
  return 0;
}

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

Теперь поговорим о лямбда-функциях. Обобщенный синтаксис анонимной лямбда-функции имеет вид:

[<режим захвата>](<тип> <переменная>, ...) -> <возвращаемый тип> { <тело функции> } (<значение переменной>, ...)

Обобщенный синтаксис лямбда-функции и вызова ее соответственно:

<возвращаемый тип> <название> = [<режим захвата>](<тип> <переменная>, ...) { <тело функции> }

<название>();

Анонимная функция всегда вызывается один раз, в месте инициализации. Следует так же отметить, что использование в качестве типа переменных auto появилось только в стандарте С++14. Теперь разберемся с режимом захвата, рассмотрев два варианта в общем плане: [&] - все переменные, находящиеся в зоне видимости, захватываются по ссылке, [=] - все переменные, находящиеся в зоне видимости, захватываются по значению. Понятно, что захват по значению осуществляется посредством копирования, а это возможно только в том случае, когда копирование разрешено (возможно разрешено только перемещение). В стандарте С++14 появляется возможность захвата с инициализацией произвольным выражением, в том числе с использованием функции move(). Варианты захвата переменных не ограничиваются двумя общими вариантами, поясню на примере: [a] - захват переменной a по значению, [a = 10, b = move(c)] - захват переменных a и b с инициализацией (доступно в С++14), [&,a] - захват всех переменных по ссылке кроме переменной a, которая захватывается по значению, [=,&a] - захват всех переменных по значению, кроме переменной a, которая захватывается по ссылке. Думаю достаточно, чтобы понять как и что работает. В заключении приведу несколько примеров применения лямбда-функций. Также отмечу, что можно заменить функтор лямбда-функцией в задачах перебора в цикле for_each и такой подход будет более современным.

int mul2(int i) {
  return 2 * i;
}

int main() {
  //анонимная функция с указанием возвращаемого типа, вывод: 3
  cout << [](int x, int y) -> int { int z = x + y; return z; }(1,2) << endl;
  //анонимная функция без указания возвращаемого типа, вывод: 7
  cout << [](int x, int y) { return x + y; }(3,4) << endl;
  //не анонимная функция, вывод: 11
  auto lambda = [](int x, int y) { int z = x + y; return z; };
  cout << lambda(5,6) << endl;
  //анонимная функция с захватом переменных
  //из области видимости по ссылке и значению, вывод: 40 30
  int i = 10, j = 20;
  cout << [i,&j]() -> int { j = 30; return i + j; }();
  cout << " " << j << endl;
  //анонимная функция-аргумент, вывод: 10
  int u = mul2([]() -> int {
    int i = 2;
    int j = 1;
    return i * i + j;
  }());
  cout << u << endl;
  return 0;
}