[ English ]

Введение в C++11: лямбда-функции

В предыдущих двух постах, я уже познакомил читателя с некоторыми интересными вещами нового стандарта. Сегодня я продолжу эту славную традицию, и расскажу о лямбда-выражениях

Новый стандарт наконец ввел очень полезную штуку — lambda-выражения. Продвинутый C++ программист скажет: "Так они уже давно есть в boost". Верно, так и есть. Но новые лямбда существенно мощнее и, на мой взгляд, удобнее. Впрочем, сравнение реализаций этих двух лямбд не относится к теме данного поста. Моя цель — дать общее представление: что это такое и как это использовать.

Не вдаваясь в истоки появления лямбд, скажу, что лямбда — это более короткая форма записи функтора. Что-то вроде анонимного функтора. Рассмотрим на примере.

Допустим, у нас есть некоторый целочисленный вектор. Задача состоит в том, чтобы отсортировать элементы так, чтобы слева находились нечетные элементы, а справа — четные.

Чтобы выполнить это, мы должны написать функтор и передать его в алгоритм std::sort.

struct Comparator : public std::binary_function<int, int, bool>
{
    bool operator()(int lhs, int rhs)const
    {
        if (lhs & 1  &&  rhs & 1)
            return lhs < rhs;
        return lhs & 1;
    }
};

std::sort(vec.begin(), vec.end(), Comparator());

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

std::sort(vec.begin(), vec.end(), [](int lhs, int rhs) -> bool {
    if (lhs & 1  &&  rhs & 1)
        return lhs < rhs;
    return lhs & 1;
});

Эта форма более наглядная и более компактная. Но давайте рассмотрим синтаксис поподробнее. В общем случае его можно записать так:

[captures](arg1, arg2) -> result_type { /* code */ }
arg1, arg2
это аргументы. То, что передается алгоритмом в функтор (лямбду).
result_type

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

Note

Стоит отметить, что если лямбда состоит из одного оператора return, то возвращаемый тип можно не писать. Например:

std::sort(vec.begin(), vec.end(), [](int lhs, int rhs) {
    return lhs & 1;
});

Теперь поговорим о captures. Это список захвата: переменных внешней среды, которые стоит сделать доступными внутри лямбды. Эти переменные можно захватывать по значению и по ссылке.

int max = 4;

// по значению
std::sort(vec.begin(), vec.end(), [max](int lhs, int rhs) {
    return lhs < max;
});
// по ссылке
std::sort(vec.begin(), vec.end(), [&max](int lhs, int rhs) {
    return lhs < max;
});

Также, можно захватить все переменные из области видимости:

// по значению
std::sort(vec.begin(), vec.end(), [=](int lhs, int rhs) {
    return lhs < someVar;
});

// по ссылке
std::sort(vec.begin(), vec.end(), [&](int lhs, int rhs) {
    return lhs < otherVar;
});

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

auto square = [](int x) { return x * x; };
std::cout << square(16) << std::endl;

Если лямбда создается в неком методе класса и необходимо обратится из неё к некоему атрибуту, то захват этого атрибута не сработает. Для того, чтобы можно было обратится к любому атрибуту/методу, необходимо захватить this, при этом ставить внутри лямбды перед атриубтом/методом this совсем не обязательно.

class Foo
{
public:
    Foo(): _x(5) {}

    void doSomething() {
        // если вместо this поставить _x — будет ошибка!
        auto lambda = [this](int x) {
            std::cout << _x * x << std::endl;
        };

        lambda(4);
    }

private:
    int _x;
};