Лямбда-выражение создаёт замыкание: объект безымянного класса с перегруженным operator(). Компилятор генерирует этот класс за вас, захваченные переменные становятся его полями.
int limit = 10;
auto small = [limit](int x) { return x < limit; };
// примерно то же, что:
struct __Lambda {
int limit;
bool operator()(int x) const { return x < limit; }
};
Захваты
[x]копияxв момент создания лямбды;[&x]ссылка наx.[=]копировать всё используемое,[&]всё по ссылке. Явный список надёжнее: видно, что именно захвачено.[this]захватывает указатель на объект,[*this](C++17) копию объекта.- Инициализирующий захват (C++14):
[p = std::move(ptr), n = v.size()]. Так переносят в лямбду move-only объекты вродеunique_ptr. - Глобальные и статические переменные не захватываются, они просто доступны.
По умолчанию operator() константный, и менять копии захваченных переменных нельзя. mutable снимает это ограничение; состояние живёт внутри объекта-замыкания, и при копировании лямбды копируется тоже.
С C++20 неявный захват this через [=] объявлен устаревшим: пишите [=, this] явно.
Висячие захваты
Захват по ссылке безопасен, только пока лямбда не переживает переменные.
std::function<int()> make_counter() {
int count = 0;
return [&count] { return ++count; }; // count умрёт при выходе: висячая ссылка
}
То же с [this] в колбэке, который вызовут после уничтожения объекта, и с лямбдами, переданными в другой поток. В отложенных вызовах захватывайте по значению или shared_from_this().
Типы лямбд
- У каждой лямбды уникальный тип, даже у двух одинаковых по тексту.
- Лямбда без захватов неявно приводится к указателю на функцию: её можно передать в C API.
[](auto x)(C++14) generic-лямбда: еёoperator()шаблонный. В C++20 можно писать явный список параметров шаблона[]<class T>(std::vector<T>& v).- С C++20 лямбды без захватов конструируются по умолчанию и допустимы в невычисляемом контексте:
std::set<int, decltype([](int a, int b) { return a > b; })>. - Лямбды по умолчанию
constexpr, если тело это позволяет (C++17).
std::function и шаблоны
std::function<R(Args...)> хранит любой вызываемый объект с подходящей сигнатурой: это стирание типа. Цена: возможная аллокация в куче (для больших замыканий, маленькие помещаются в буфер внутри объекта), косвенный вызов, который компилятор обычно не встраивает. Пустой std::function при вызове бросает std::bad_function_call.
Поэтому:
- если вызываемый объект нужен только внутри функции, принимайте его шаблонным параметром
template <class F> void for_each(F f)илиstd::invocableв C++20: вызов встроится; std::functionхорош, когда нужно хранить разнородные колбэки в контейнере или поле класса.
В C++23 появился std::move_only_function для move-only замыканий.
Рекурсивные лямбды
Лямбда не видит собственное имя внутри тела. Варианты: передать её самой себе (auto f = [](auto self, int n) -> int { ... self(self, n - 1) ... }), обернуть в std::function, или в C++23 явный объектный параметр [](this auto self, int n).
Частые ошибки
[&]в колбэке, который вызовут позже.- Ожидание, что
[x]увидит последующие измененияx. - Изменение захваченной по значению переменной без
mutable(ошибка компиляции) или сmutable, но в копии лямбды. std::functionв горячем цикле, где хватило бы шаблона.
Что спросят на собесе
- Во что компилятор превращает лямбду.
- Захват по значению и по ссылке, init-capture,
mutable. - Когда захват по ссылке опасен.
- Чем
std::functionотличается от шаблонного параметра и сколько стоит. - Как сделать рекурсивную лямбду.