Что такое лямбда-выражение в C++ и когда его нужно использовать?
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int multiplier = 3;
// Что здесь происходит?
std::for_each(numbers.begin(), numbers.end(), [multiplier](int n) {
std::cout << n * multiplier << " ";
});
return 0;
}
Я понимаю, что `std::for_each` применяет функцию к каждому элементу, но меня смущает конструкция `[multiplier](int n) { ... }`. Почему нельзя просто написать обычную функцию или функтор? В чем преимущество лямбд?
Также я не понимаю, что означают квадратные скобки `[]`. В одних примерах они пустые, в других — с переменными внутри. И когда нужно использовать `auto` с лямбдами?
Буду благодарен за простое объяснение с примерами, когда лямбды реально упрощают код, а когда лучше использовать старый добрый функтор. Заранее спасибо!
<<Лямбда-выражение: анонимная функция "на лету"
Лямбда-выражение в C++ — это, по сути, анонимная функция, которую можно создать прямо в месте использования. Вместо того чтобы писать отдельную функцию или класс-функтор, вы описываете поведение прямо там, где оно нужно. Это делает код более компактным и читаемым, особенно при работе с алгоритмами STL.
Давайте разберем синтаксис на вашем примере:
[multiplier](int n) {
std::cout << n * multiplier << " ";
}
Здесь:
[multiplier]— захват (capture). Квадратные скобки определяют, какие переменные из внешней области видимости будут доступны внутри лямбды. Если скобки пустые[], то доступ к внешним переменным запрещен. Если написать[=], все переменные захватываются по значению,[&]— по ссылке. Можно комбинировать:[=, &multiplier](все по значению, кроме multiplier по ссылке).(int n)— список параметров, как у обычной функции.{ ... }— тело лямбды.
Когда использовать лямбды?
Лямбды идеальны для коротких, одноразовых операций. Вот три главных сценария:
1. В алгоритмах STL (самое частое применение)
Вместо того чтобы писать отдельную функцию сравнения для std::sort или предикат для std::find_if, вы пишете лямбду прямо в вызове:
std::vector<int> v = {4, 2, 5, 1, 3};
// Сортировка по убыванию без отдельной функции
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});
// Поиск первого четного числа
auto it = std::find_if(v.begin(), v.end(), [](int n) {
return n % 2 == 0;
});
2. Захват контекста (closure)
Это killer feature лямбд. Вы можете "захватить" переменные из внешней функции и использовать их внутри:
int threshold = 10;
std::vector<int> data = {5, 15, 3, 20};
// Удаляем элементы меньше threshold
data.erase(std::remove_if(data.begin(), data.end(),
[threshold](int x) { return x < threshold; }), data.end());
// Результат: {15, 20}
С обычным функтором вам пришлось бы создавать класс с полем для хранения threshold.
3. Передача лямбды как callback
Лямбды отлично подходят для обработчиков событий или асинхронных задач:
void processData(const std::vector<int>& data,
std::function<void(int)> callback) {
for (int x : data) {
if (x % 2 == 0) callback(x);
}
}
int main() {
std::vector<int> nums = {1,2,3,4,5};
processData(nums, [](int n) {
std::cout << "Even: " << n << std::endl;
});
}
Когда НЕ использовать лямбды?
- Сложная логика (более 5-10 строк). Выносите в именованную функцию.
- Повторное использование. Если одна и та же логика нужна в нескольких местах — создайте функтор или функцию.
- Рекурсия. Лямбды не могут вызывать себя напрямую (без
std::functionи трюков).
Про auto и лямбды
Каждая лямбда имеет уникальный тип, который нельзя назвать явно. Поэтому auto — единственный способ сохранить лямбду в переменную:
auto myLambda = [](int a, int b) { return a + b; };
std::cout << myLambda(3, 4); // 7
Итог: лямбды делают код более локальным, избавляют от "мусорных" функций и функторов, и позволяют элегантно захватывать контекст. Используйте их для коротких операций внутри алгоритмов и callback'ов.
<<Лямбды vs функторы: когда что выбрать?
Первый ответ отлично объяснил синтаксис. Я добавлю сравнение с функторами, чтобы стало понятнее, когда лямбды — это просто сахар, а когда — необходимость.
Функтор — это класс с перегруженным оператором (). До C++11 это был единственный способ создать "объект с состоянием" для алгоритмов:
struct Multiplier {
int factor;
Multiplier(int f) : factor(f) {}
int operator()(int x) const { return x * factor; }
};
std::transform(v.begin(), v.end(), v.begin(), Multiplier(3));
Лямбда делает то же самое компактнее:
int factor = 3;
std::transform(v.begin(), v.end(), v.begin(), [factor](int x) { return x * factor; });
Когда функтор лучше? Если у вас сложное состояние (несколько полей, методы), которое нужно инициализировать и изменять. Функтор — это полноценный класс с инкапсуляцией. Лямбда же — легковесная "одноразовая" конструкция.
Когда лямбда незаменима? Когда нужно захватить локальные переменные по ссылке и изменить их внутри (например, подсчитать количество элементов):
int count = 0;
std::for_each(v.begin(), v.end(), [&count](int x) {
if (x > 5) count++;
});
С функтором для этого пришлось бы делать поле-ссылку, что менее удобно.
Вывод: для простых предикатов и операций преобразования — лямбда. Для сложной логики с состоянием — функтор или отдельная функция.