Avatar ·

Что такое лямбда-выражение в C++ и когда его нужно использовать?

Всем привет! Я изучаю C++ и часто вижу в коде коллег что-то вроде этого: ```cpp std::vector numbers = {5, 2, 8, 1, 9}; std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a < b; }); ``` Или вот так: ```cpp std::for_each(numbers.begin(), numbers.end(), [](int& x) { x *= 2; }); ``` Я понимаю, что это какие-то анонимные функции, но не до конца разбираюсь. В документации написано "лямбда-выражения", но объяснения какие-то сложные. У меня несколько вопросов: 1. Что такое лямбда-выражение простыми словами? Чем оно отличается от обычной функции? 2. Зачем нужны квадратные скобки `[]` в начале? Что туда можно писать? 3. Когда лучше использовать лямбду, а когда — обычную функцию или функтор? 4. Как правильно захватывать переменные? Я видел `[=]`, `[&]`, `[this]` — что это значит? Особенно меня пугает синтаксис с захватом. Я пытался написать свой callback для сортировки с использованием внешней переменной, и у меня ничего не получилось. Объясните, пожалуйста, на пальцах, с примерами. Заранее спасибо! <<>>

Что такое лямбда-выражение?

Лямбда-выражение (или просто лямбда) — это анонимная функция, которую вы можете создать прямо в месте использования. В C++11 и новее лямбды стали мощным инструментом для написания компактного и выразительного кода. По сути, это синтаксический сахар над функтором (объектом с перегруженным оператором ()).

Базовая структура лямбды:

[захват](параметры) -> возвращаемый_тип { тело }

Пример простой лямбды:

auto sum = [](int a, int b) { return a + b; };
int result = sum(5, 3); // result = 8

Зачем нужны квадратные скобки [ ]?

Это список захвата (capture list). Он определяет, какие переменные из внешней области видимости будут доступны внутри лямбды и как именно — по значению или по ссылке.

Основные варианты:

  • [] — ничего не захватываем
  • [x] — захватываем x по значению
  • [&x] — захватываем x по ссылке
  • [=] — захватываем все используемые переменные по значению
  • [&] — захватываем все используемые переменные по ссылке
  • [this] — захватываем указатель на текущий объект (для методов класса)
  • [=, &x] — все по значению, кроме x (по ссылке)

Когда использовать лямбду?

Лямбды идеальны в следующих ситуациях:

  • Короткие предикаты для алгоритмов STL (сортировка, поиск, фильтрация)
  • Обработчики событий и callback-функции
  • Временная логика, которая не заслуживает отдельной функции
  • Замыкания — когда нужно "запомнить" состояние

Пример с захватом переменной:

int threshold = 5;
std::vector<int> data = {1, 7, 3, 9, 2};

// Захватываем threshold по значению
auto count = std::count_if(data.begin(), data.end(), 
    [threshold](int x) { return x > threshold; });
// count = 2 (7 и 9)

// Захватываем по ссылке, чтобы изменить
int sum = 0;
std::for_each(data.begin(), data.end(), 
    [&sum](int x) { sum += x; });
// sum = 22

Когда НЕ стоит использовать лямбду?

  • Если логика сложная (больше 5-10 строк) — лучше вынести в именованную функцию
  • Если лямбда нужна в нескольких местах — создайте функтор или обычную функцию
  • Если важна читаемость и самодокументируемость кода

Продвинутые возможности (C++14 и C++17)

Начиная с C++14 лямбды стали ещё мощнее:

// Обобщённые лямбды (auto параметры)
auto generic = [](auto a, auto b) { return a + b; };

// Захват с инициализацией
auto lambda = [x = std::move(some_value)]() { /* используем x */ };

// constexpr лямбды (C++17)
constexpr auto square = [](int x) { return x * x; };
static_assert(square(5) == 25);

Важные замечания

  • Будьте осторожны с захватом по ссылке — убедитесь, что переменная живёт дольше лямбды
  • По умолчанию operator() у лямбды — const, то есть вы не можете изменять захваченные по значению переменные. Используйте mutable если нужно: [x]() mutable { x++; }
  • Лямбды — это объекты, их можно хранить в std::function или передавать как шаблонные параметры

Надеюсь, теперь стало понятнее! Лямбды — это мощный инструмент, который делает код на C++ более современным и выразительным. Практикуйтесь, и скоро вы будете использовать их автоматически.

<<>>

Альтернативный взгляд: лямбды как замена функторов

Если вы знакомы с функторами (классами с оператором ()), то лямбды можно рассматривать как их более краткую версию. Сравните:

// Функтор
struct GreaterThan {
    int threshold;
    GreaterThan(int t) : threshold(t) {}
    bool operator()(int x) const { return x > threshold; }
};
std::count_if(v.begin(), v.end(), GreaterThan(5));

// Лямбда (то же самое, но короче)
std::count_if(v.begin(), v.end(), [threshold = 5](int x) { return x > threshold; });

Лямбды особенно удобны, когда нужно создать "одноразовый" функтор прямо на месте. Они избавляют от необходимости писать отдельный класс, что уменьшает количество кода и улучшает читаемость.

Ещё один полезный приём — использование лямбд для отложенного выполнения (lazy evaluation):

auto expensiveCalculation = [](int x) { 
    // Долгие вычисления
    return x * x + 42; 
};

// Вычисление произойдёт только при вызове
int result = expensiveCalculation(10);

Совет: если ваш компилятор поддерживает C++20, обратите внимание на шаблонные лямбды с явными параметрами шаблона:

auto lambda = []<typename T>(T a, T b) { return a + b; };

Это даёт ещё больше контроля над типами.