Лямбда‑функции в C++: захваты, mutable, возвращаемые типы и лучшие практики
Запрос, под который оптимизирована статья: лямбда‑функции в C++. Если вы хотите быстро и безопасно писать компактные функции прямо в месте вызова — лямбды незаменимы. Разберёмся на практических примерах, без лишней теории.
Базовый синтаксис лямбда‑функций
Общий вид: [](параметры) спецификаторы -> возвращаемый_тип { тело; }. Минимальная лямбда:
#include <iostream>
int main() {
auto hello = [] { std::cout << "Hello, lambda!n"; };
hello();
}
Захваты: по значению, по ссылке и смешанные
Захват управляет тем, какие внешние переменные доступны внутри лямбды.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v{5, 1, 4, 2, 3};
int pivot = 3;
// Сортируем так, чтобы числа < pivot были первыми
std::sort(v.begin(), v.end(), [=](int a, int b) {
auto ka = (a < pivot);
auto kb = (b < pivot);
if (ka != kb) return ka > kb; // < перед > — логика группировки
return a < b;
});
for (int x : v) std::cout << x << ' ';
}
Совет: по умолчанию используйте явный захват (например, [pivot]). Глобальные [=] и [&] удобны, но повышают риск случайно захватить лишнее и усложнить сопровождение.
Инициализирующий и перемещающий захват (C++14+)
Можно создавать новые переменные прямо в списке захвата — удобно для перемещения уникальных ресурсов или подготовки состояния.
#include <memory>
#include <thread>
#include <iostream>
int main() {
auto p = std::make_unique<int>(42);
std::thread t([q = std::move(p)]() {
// q - единственный владелец внутри лямбды
std::cout << *q << "n";
});
t.join();
// p теперь пуст (nullptr)
}
Такой приём безопасен в многопоточных сценариях: вы явно передаёте владение в лямбду.
mutable: изменение копий, а не внешних значений
Лямбды по умолчанию «константные»: захваченные по значению переменные менять нельзя. mutable снимает это ограничение, но меняет только копии.
#include <iostream>
int main() {
int n = 0;
auto inc_copy = [n]() mutable {
n++; // ок, меняем копию
return n;
};
std::cout << inc_copy() << ", " << inc_copy() << "n"; // 1, 2
std::cout << n << "n"; // исходный n по-прежнему 0
}
Чтобы менять внешнюю переменную, захватывайте по ссылке: [&n]{ n++; }.
Возвращаемые типы: auto и явное указание
Компилятор выводит тип результата автоматически, но если в ветках возвращаются разные типы, нужна явная аннотация.
auto f = [](bool flag) {
if (flag) return 1; // int
// return 1.5; // ошибка: разные типы
return 2; // ок, везде int
};
auto g = [](bool flag) -> double {
if (flag) return 1; // 1.0
return 1.5; // 1.5
};
Generic‑лямбды (C++14+): параметры как auto
Идеально подходят для алгоритмов стандартной библиотеки и универсальных коллбеков без явного шаблонного синтаксиса.
#include <algorithm>
#include <cctype>
#include <string>
#include <vector>
int main() {
std::vector<std::string> words{"C++", "Lambda", "Rocks"};
std::transform(words.begin(), words.end(), words.begin(), [](auto s) {
for (auto& ch : s) ch = std::toupper(static_cast(ch));
return s;
});
}
Лямбды в алгоритмах: фильтрация и подсчёт
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v{1,2,3,4,5,6};
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x % 2 == 0; }), v.end());
// Остались только нечётные: 1 3 5
for (int x : v) std::cout << x << ' ';
}
std::function и лямбды: когда это оправдано
std::function — тип-обёртка для хранение вызываемого объекта с типовым стиранием. Это гибко, но медленнее и требует выделений. Рекомендации:
auto или передавайте как универсальный параметр (perfect forwarding).std::function<...> для стабильного интерфейса.#include <functional>
#include <iostream>
void run(std::function<void(int)> cb) { cb(42); }
int main() {
auto fast = [](int x){ std::cout << x << "n"; };
run(fast); // удобно на границе API
}
Статeless‑лямбда как указатель на функцию
Если лямбда ничего не захватывает, её можно неявно преобразовать к указателю на функцию подходящей сигнатуры — удобно для старых C‑API.
void (*fp)(int) = [](int x){ /* ... */ };
fp(10);
Типичные ошибки и как их избежать
-> тип явно.Небольшой чек‑лист по лямбдам
[x], [&y], [z = make()].mutable, когда меняете копию захваченного значения внутри лямбды.auto для хранения лямбды, а std::function — для публичных API.[p = std::move(p)]).Куда двигаться дальше
Потренируйтесь переписать свои sort/filter/remove_if на лямбды, попробуйте перемещающий захват и generic‑лямбды. А чтобы системно закрыть пробелы по современному C++ (включая лямбды, ООП, STL и практику), посмотрите программу и первые уроки здесь: C++ с Нуля до Гуру — посмотреть программу и начать бесплатно.



