Лямбда-функции и замыкания

Лямбда-функция – вариант реализации функциональных объектов в языке С++11, обязанный своим названием λ-исчислению – математической системе определения и применения функций, в которой аргументом одной функции может быть другая функция. Лямбда-функции в основном определяются в точке их применения. Лямбда-функции могут использоваться всюду, где требуется передача вызываемому объекту функции соответствующего типа.

Лямбда-функции могут использовать внешние по отношению к ней самой племенные, образующие замыкание такой функции. В С++ лямбда-функции определяются как:

[]() { }

Где [] - лист “захваченных переменных”, () - параметры функции, {} - тело функции

Например:

bool foo(int x) { return x % m == 0; }

эквивалентно

[] (int x) { return x % m == 0; }

Правила оформления лямбда-функций

При преобразовании функции языка C++ в лямбда-функцию необходимо учитывать, что имя функции заменяется в лямбда-функции квадратными скобками [], а возвращаемый тип:

  • не определяется явно (слева)
  • при анализе лямда-функции с телом вида return expr;
  • при отсутствии в теле однооператорной лямбда-функции оператора return автоматически принимается равным void
  • в остальных случаях задается программистом при помощи “хвостового” способа записи:
[](int x) -> int { int y = x; return x  y; }

Преимущества лямбда-функций

  • Близость – определяются в месте их дальнейшего применения
  • Краткость – немногословны и могут быть использованы повторно
  • Эффективность – могут встраиваться компилятором в точку определения на уровне объектного кода

Анонимные лямбда-функции

Рассмотрим следующую функцию:

#include<iostream>
#include<vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
    vector<int> odd;
    for (int i = 0; i < v.size(); ++i) {
        if (v[i] % 2 == 1) {
            odd.push_back(v[i]);
        }
    }

    for (int i = 0; i < odd.size(); ++i) {
        cout << odd[i] << endl;
    }
    return 0;
}

Можно заменить на:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
    vector<int> odd;
    copy_if(v.begin(), v.end(), std::back_inserter(odd), [](int i) { return i % 2 == 1; });
    for (int i = 0; i < odd.size(); ++i) {
        cout << odd[i] << endl;
    }
    return 0;
}

Любую анонимную лямбда-функцию можно сделать именной:

auto isOdd = [](int i) { return i % 2 == 1; };
copy_if(v.begin(), v.end(), std::back_inserter(odd), isOdd);

Также с помощью лямбда-функции можно менять значение на месте (in-place) как показано в примере ниже:

string s = "hello";
transform(s.begin(), s.end(), s.begin(), [](unsigned char c) -> unsigned char { return toupper(c); });
cout << s;

Внешние переменные и замыкание лямбда-функций

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

  • [z] – доступ по значению к одной переменной z
  • [&z] – доступ по ссылке к одной переменной z
  • [=] – доступ по значению ко всем переменным
  • [&] – доступ по ссылке ко всем переменным
  • [&, z], [=, &z], [z, &zz] – смешанный вариант доступа.

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

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
    int countN = count_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
    cout << countN;
    return 0;
}

Этот же пример можно переписать использую замыкание:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
    int countN = 0;
    for_each(v.begin(), v.end(), [&countN](int x) { countN += x % 2 == 0; });
    cout << countN;
    return 0;
}