В современном C++ лямбда-выражения и функторы (объекты-функции) являются мощными инструментами для создания гибкого и выразительного кода. Они позволяют инкапсулировать поведение и передавать его как объекты, что особенно полезно при работе с алгоритмами STL, асинхронными операциями и многими другими сценариями.
Функторы (объекты-функции) в C++
Что такое функторы?
Функтор — это объект класса, для которого перегружен оператор вызова operator(). Это позволяет использовать объект как функцию.
class Adder {
public:
Adder(int x) : x_(x) {}
int operator()(int y) const {
return x_ + y;
}
private:
int x_;
};
int main() {
Adder add5(5);
cout << add5(3); // Выведет 8
}
Преимущества функторов
- Состояние: Функторы могут хранить состояние между вызовами
- Инкапсуляция: Можно скрыть сложную логику внутри класса
- Гибкость: Можно использовать шаблоны и наследование
- Производительность: Компилятор может оптимизировать вызовы лучше, чем указатели на функции
Лямбда-выражения в C++
Основы лямбда-выражений
Лямбда-выражение — это анонимная функция, которая может захватывать переменные из окружающей области видимости.
Базовый синтаксис:
[захват](параметры) -> возвращаемый_тип { тело_функции }
Пример:
auto lambda = [](int a, int b) { return a + b; };
cout << lambda(2, 3); // 5
Захват переменных
Лямбда-выражения могут захватывать переменные из окружающего контекста:
По значению:
int x = 10;
auto lambda = [x]() { cout << x; };
По ссылке:
int y = 20;
auto lambda = [&y]() { y++; };
Захват всего контекста:
[=]() {}; // Все по значению
[&]() {}; // Все по ссылке
Возвращаемый тип
Компилятор может выводить возвращаемый тип автоматически, но его можно указать явно:
auto lambda = []() -> int { return 42; };
Изменяемые лямбды
По умолчанию operator() лямбды const. Для изменения захваченных по значению переменных используйте mutable:
int counter = 0;
auto lambda = [counter]() mutable { counter++; };
Сравнение функторов и лямбда-выражений
Сходства
- Оба могут иметь состояние
- Оба могут быть переданы как объекты функций
- Оба могут использоваться с шаблонами
Различия
- Синтаксис: Лямбды более компактны
- Захват контекста: Лямбды могут легко захватывать локальные переменные
- Генерируемый код: Лямбда — это синтаксический сахар для функтора
Пример эквивалентности:
// Лямбда
auto lambda = [x](int y) { return x + y; };
// Эквивалентный функтор
class LambdaEquivalent {
public:
LambdaEquivalent(int x) : x_(x) {}
int operator()(int y) const { return x_ + y; }
private:
int x_;
};
Продвинутые техники
Шаблонные лямбды (C++14)
В C++14 появились лямбды с auto параметрами:
auto lambda = [](auto x, auto y) { return x + y; };
Инициализация в захвате (C++14)
Можно инициализировать переменные прямо в захвате:
auto lambda = [value = 42]() { return value; };
constexpr лямбды (C++17)
Лямбды могут быть constexpr:
constexpr auto square = [](int x) { return x * x; };
static_assert(square(5) == 25);
Рекурсивные лямбды
Для рекурсии нужно использовать std::function:
std::function<int(int)> factorial = [&factorial](int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
};
Практическое применение
Использование с алгоритмами STL
vector<int> v = {1, 2, 3, 4, 5};
// Умножить каждый элемент на 2
transform(v.begin(), v.end(), v.begin(), [](int x) { return x * 2; });
Асинхронные операции
auto future = async([]() {
this_thread::sleep_for(1s);
return 42;
});
Пользовательские компараторы
sort(v.begin(), v.end(), [](auto a, auto b) { return a > b; });
Лямбда-выражения и функторы — это мощные инструменты в C++, которые позволяют писать более выразительный и гибкий код. Лямбды, появившиеся в C++11, значительно упростили написание анонимных функций, в то время как функторы предоставляют больше контроля и возможностей для сложных сценариев.
