Avatar ·

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

Привет, коллеги! Я начал изучать C++11 и постоянно натыкаюсь на термин "лямбда-выражение" (lambda expression). Читал документацию, но не до конца понимаю, что это такое и, главное, зачем оно нужно. Вот пример, который я нашел в одном туториале:
#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++;
});

С функтором для этого пришлось бы делать поле-ссылку, что менее удобно.

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