Ответ
Лямбда-выражения (начиная с 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 их уже и в шаблоны пихать можно. В общем, инструмент на все случаи жизни, чувак. Выучил синтаксис — и жизнь стала проще, ебать мои старые костыли.