Лямбда-выражения и функторы в C++

Автор: | 15 июня, 2025

Современный подход к функциональному программированию

Современный язык программирования C++ предлагает множество инструментов для написания лаконичного, эффективного и выразительного кода. Одним из таких инструментов являются лямбда-выражения и функторы, которые позволяют применять элементы функционального программирования в императивной парадигме.

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


Что такое функторы в C++?

Функтор (от английского functor, также известный как функциональный объект) — это объект класса, поведение которого имитирует поведение функции, благодаря перегрузке оператора operator().

Пример:

#include <iostream>

class Adder {
public:
    Adder(int x) : base(x) {}

    int operator()(int y) const {
        return base + y;
    }

private:
    int base;
};

int main() {
    Adder add_five(5);
    std::cout << add_five(10) << std::endl; // Выведет 15
}

Преимущества функторов:

  • Могут хранить состояние (в отличие от обычных функций).
  • Поддерживают инкапсуляцию и повторное использование.
  • Могут быть использованы как объекты с полным контролем (например, передаваться в шаблоны или STL-алгоритмы).

Что такое лямбда-выражения?

Лямбда-выражения — это анонимные функции, которые можно определять прямо в месте использования. Они были введены в C++11 и существенно упростили функциональный стиль программирования.

Синтаксис лямбда:

[capture](parameters) -> return_type {
    // тело
}

Пример:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 5, 3, 7, 2};

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

    for (int n : vec)
        std::cout << n << " ";
}

Захват переменных (capture):

  • [ ] — ничего не захватывать.
  • [=] — захват всех переменных по значению.
  • [&] — захват всех переменных по ссылке.
  • [x] — захват x по значению.
  • [&x] — захват x по ссылке.

Сравнение: Функторы vs. Лямбда-выражения

КритерийФункторыЛямбда-выражения
ОпределениеЧерез класс с перегрузкой operator()Встроено в язык (анонимная функция)
СостояниеМожет сохранять внутреннее состояниеМожет захватывать переменные
ПроизводительностьПотенциально чуть быстрее при инлайнеЧасто сопоставима с функтором
ГибкостьВысокая (можно переопределять методы)Ограничено телом лямбды
Используемость в шаблонахОтличноОтлично
ЧитаемостьМенее читаемо при сложной логикеБолее читаемо для кратких выражений

Примеры использования в STL

1. std::sort с функтором:

struct Descending {
    bool operator()(int a, int b) const {
        return a > b;
    }
};

std::sort(vec.begin(), vec.end(), Descending());

2. std::sort с лямбдой:

std::sort(vec.begin(), vec.end(), [](int a, int b) {
    return a > b;
});

3. std::for_each с захватом переменной:

int sum = 0;
std::for_each(vec.begin(), vec.end(), [&sum](int n) {
    sum += n;
});
std::cout << "Сумма: " << sum << std::endl;

Внутренности: как работают лямбды под капотом?

При компиляции лямбда преобразуется в неименованный функтор, то есть класс с operator() и полями, соответствующими захваченным переменным.

auto f = [x](int y) { return x + y; };

Компилятор создаст нечто вроде:

struct LambdaGenerated {
    int x;
    int operator()(int y) const {
        return x + y;
    }
};

Дополнительные возможности:

1. Мутирующие лямбды:

По умолчанию operator() у лямбды — const. Чтобы изменить захваченные по значению переменные внутри лямбды, нужно использовать mutable.

int x = 5;
auto f = [x]() mutable {
    x++;
    std::cout << x;
};
f(); // 6

2. Возвращаемое значение (return type):

auto f = [](double x) -> int {
    return static_cast<int>(x);
};

Когда использовать что?

СитуацияРекомендация
Простая логика внутри STLЛямбда
Многоразовое использованиеФунктор
Наличие состояния, сохраняемого между вызовамиФунктор
Быстрое определение функции в месте вызоваЛямбда

Заключение

Функторы и лямбда-выражения — мощные инструменты языка C++, позволяющие писать более выразительный и модульный код. С введением лямбд многие привычные паттерны можно реализовать проще и компактнее. Тем не менее, функторы по-прежнему остаются важной частью инструментов C++, особенно когда требуется более сложное поведение или повторное использование.

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