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

На других языках: English

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

Новый стандарт наконец ввел очень полезную штуку — 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

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

Стоит отметить, что если лямбда состоит из одного оператора 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;
};
Комментарии (9):
@Fl0

Прямо c++11 для чайников :) ждёмс продолжения.

> ответить
@Марина

Полезные статьи, а то на парах рассказывали не очень обширно, приходится теперь самой все искать!

> ответить
@ikalnitsky
Прямо c++11 для чайников :)

Так, в принципе, и задумывалось... Везде либо английские тексты, либо урезанные и непонятные переводы. Вот и пишу, для меньшего собрата, кто еще не познал дзен в англоязычной литературе :)

> ответить
@GooRoo
«Не вдаваясь в истоки появления лямбд, скажу, что лямбда -- это более короткая форма записи функтора».

Ну, это ты нехило так хватанул и не вдался в историю. Следует уточнить, что твоё утверждение корректно только в контексте C++.

> ответить
@ikalnitsky
Ну, это ты нехило так хватанул и не вдался в историю. Следует уточнить, что твоё утверждение корректно только в контексте C++.

Что-то не вижу в посте ничего такого, из-за чего могло бы возникнуть ощущение, что речь идет не о C++. Да и внимательней будь:

Не вдаваясь в истоки появления лямбд..

а это означает, что я не рассматриваю теорию лямбда-исчислений в целом.

> ответить
@GooRoo

Будем считать, что я неправильно тебя понял, сорри ;]

> ответить
@Zoresvit

Сенкс! И сделай себе раскладку со спец-символами, или мап какой-нибудь на тире вместо "--". А то как будто RFC читаю :)

> ответить
@ikalnitsky

Zoresvit, это проблемы отсутствия времени :) сейчас у меня используется Markdown как средство форматирования текста. Но он, увы, не хавает тире. Как только будет время -- обязательно это исправлю и добавлю пару расширений. :)

> ответить
@аноним

Спасибо большое автору! Читаю поочередно серию статей о С++11, все описано просто замечательно!

> ответить



Comment system uses reStructuredText. [show/hide tip]