Статьи → Введение в 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
это тип возвращаемого значения. Это может показаться несколько непривычно, так как раньше тип всегда писали перед сущностью (переменной, функцией). Но к этом быстро привыкаешь.
Стоит отметить, что если лямбда состоит из одного оператора 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; };
Прямо c++11 для чайников :) ждёмс продолжения.
Полезные статьи, а то на парах рассказывали не очень обширно, приходится теперь самой все искать!
Так, в принципе, и задумывалось... Везде либо английские тексты, либо урезанные и непонятные переводы. Вот и пишу, для меньшего собрата, кто еще не познал дзен в англоязычной литературе :)
Ну, это ты нехило так хватанул и не вдался в историю. Следует уточнить, что твоё утверждение корректно только в контексте C++.
Что-то не вижу в посте ничего такого, из-за чего могло бы возникнуть ощущение, что речь идет не о C++. Да и внимательней будь:
а это означает, что я не рассматриваю теорию лямбда-исчислений в целом.
Будем считать, что я неправильно тебя понял, сорри ;]
Сенкс! И сделай себе раскладку со спец-символами, или мап какой-нибудь на тире вместо "--". А то как будто RFC читаю :)
Zoresvit, это проблемы отсутствия времени :) сейчас у меня используется Markdown как средство форматирования текста. Но он, увы, не хавает тире. Как только будет время -- обязательно это исправлю и добавлю пару расширений. :)
Спасибо большое автору! Читаю поочередно серию статей о С++11, все описано просто замечательно!