Для чего нужна лямбда-функция (lambda) в 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 контекстах и шаблонные параметры для лямбд.

Ответ 18+ 🔞

А, лямбды! Ну это ж, ёпта, просто подарок для лытающего от писанины отдельных классов программиста. Раньше, блядь, чтобы в std::sort свою логику запихнуть, надо было либо функцию писать где-то на отшибе, либо целый класс-функтор городить — терпения ноль ебать. А теперь — раз, и прямо в месте использования слепил анонимную функцию, и всё работает. Красота, ядрёна вошь!

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

1. Самый ходовой случай — алгоритмы STL. Вот смотри, без лямбд это был бы пиздец какой-то геморрой.

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

// Сортировка по убыванию. Раньше писали `std::greater<int>()`, а теперь — своя логика на коленке.
std::sort(numbers.begin(), numbers.end(),
          [](int a, int b) { return a > b; }); // Всё, отсортировали. Кратко и ясно.

// Найти первое чётное число. Раньше бы функтор `IsEven` писали, а сейчас...
auto it = std::find_if(numbers.begin(), numbers.end(),
                       [](int n) { return n % 2 == 0; }); // И всё, чувак.

// А вот тут уже интереснее — нужны данные снаружи.
int threshold = 4;
// Захватываем `threshold` из внешнего контекста и используем внутри.
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;

Видишь, как удобно? Вся логика фильтрации — прямо перед глазами, не надо прыгать по файлу в поисках функции isGreaterThanThreshold.

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] — осторожно, бля! Если `prefix` умрёт раньше, чем вызовут лямбду, будет тебе вротберунчик и неопределённое поведение.
// Ещё есть тупой захват всего подряд: [=] или [&]. Так делать — признак распиздяя. Явно перечисляй, что захватываешь, чтобы самому потом не охуеть от багов.

3. Асинхронщина и потоки — тут они просто незаменимы. Представь, что нужно запустить что-то тяжёлое в фоне.

#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;

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

В чём, блядь, соль-то?

  • Всё в одном месте: Не надо скакать глазами по коду в поисках, что же там делает этот предикат. Вся логика — прямо тут, под рукой.
  • Гибкость овердохуища: Захотел особую логику сравнения — набросал её за секунду, прямо в вызове алгоритма.
  • Быстрота: Умный компилятор часто просто встраивает код лямбды прямо в место вызова, так что потерь на вызов функции — ноль. Это не то что старые указатели на функции, которые могли куда угодно прыгнуть.

А ещё они эволюционируют, как покемоны. В C++14 научились параметры с auto принимать (обобщённые лямбды), в C++17 — быть constexpr, а в C++20 их уже и в шаблоны пихать можно. В общем, инструмент на все случаи жизни, чувак. Выучил синтаксис — и жизнь стала проще, ебать мои старые костыли.