Для чего нужна лямбда-функция (lambda) в C++?

«Для чего нужна лямбда-функция (lambda) в C++?» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Базовый синтаксис: [захват](параметры) -> возвращаемый_тип { тело }. Возвращаемый тип часто выводится автоматически.

1. Использование с алгоритмами STL (самый частый случай):

std::vector<int> numbers = {1, 5, 3, 8, 2};

// Сортировка по убыванию
std::sort(numbers.begin(), numbers.end(),
          [](int a, int b) { return a > b; });

// Поиск первого четного числа
auto it = std::find_if(numbers.begin(), numbers.end(),
                       [](int n) { return n % 2 == 0; });

// Подсчет чисел, больших заданного порога
int threshold = 4;
int count = std::count_if(numbers.begin(), numbers.end(),
                          [threshold](int n) { return n > threshold; });
std::cout << count << " numbers are greater than " << threshold << std::endl;

2. Захват переменных из окружающего контекста: Способ передачи внешних данных в лямбду.

int base = 100;
std::string prefix = "Result: ";

// Захват по значению [base, prefix]
auto adder = [base, prefix](int x) {
    return prefix + std::to_string(base + x);
};
// Захват по ссылке [&base, &prefix] - осторожно с временами жизни!
// Захват всего контекста по значению [=] или по ссылке [&] (не рекомендуется)

3. Использование в асинхронных задачах (std::async, потоки):

#include <future>
#include <thread>

int computeSomething() { /* долгий расчет */ return 42; }

// Запуск вычисления в отдельном потоке
std::future<int> result = std::async(std::launch::async, []() {
    return computeSomething();
});

// Делаем что-то ещё в основном потоке
std::cout << "Waiting for result..." << std::endl;

// Получаем результат (блокируемся, если не готов)
int value = result.get();
std::cout << "Result is: " << value << std::endl;

Ключевые преимущества:

  • Инкапсуляция логики: Код обработки находится прямо рядом с вызовом алгоритма, улучшая читаемость.
  • Гибкость: Легко создавать специализированные операции "на лету".
  • Производительность: Компилятор часто может заинлайнить тело лямбды, устраняя накладные расходы на вызов функции.

Эволюция в стандартах: C++14 добавил обобщенные лямбды (auto в параметрах), C++17 — constexpr лямбды, C++20 — лямбды в unevaluated контекстах и шаблонные параметры для лямбд.