Lambda 表达式与函数式编程
掌握 C++ Lambda 表达式──捕获列表、可变 Lambda、泛型 Lambda 及其作为算法谓词的用法。学习 std::function、std::invoke 和 IIFE 模式。
Lambda 表达式
Lambda 表达式创建一个匿名函数对象(闭包)。Lambda 是向 STL 算法、回调和事件处理器传递行为的主要方式。
完整语法为:
[captures](parameters) mutable -> return_type { body }- 捕获列表 ── 从外层作用域中要在 Lambda 内使用的变量
- 参数 ── 与普通函数参数类似
- mutable ── 允许修改按值捕获的变量
- 返回类型 ── 通常自动推导
- 函数体 ── 函数体
Lambda 编译后会成为编译器生成的函数对象(带有 operator() 的结构体)──与手写函数对象相比零开销。
基本 Lambda 语法和捕获
捕获列表是 Lambda 的标志性特征。它决定了 Lambda 可以访问哪些外层变量以及如何访问。
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 5, 3, 8, 2, 9, 4, 7, 6};
// Simplest lambda — no captures
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; }); // descending
// Capture by value [=] — copies variables
int threshold = 5;
auto above = std::count_if(nums.begin(), nums.end(),
[threshold](int x) { return x > threshold; });
std::cout << "Above " << threshold << ": " << above << '\n';
// Capture by reference [&] — references variables
int sum = 0;
std::for_each(nums.begin(), nums.end(),
[&sum](int x) { sum += x; });
std::cout << "Sum: " << sum << '\n';
// Mixed captures
int min_val = 3, max_val = 7;
auto in_range = std::count_if(nums.begin(), nums.end(),
[min_val, &max_val](int x) { return x >= min_val && x <= max_val; });
// min_val captured by value, max_val captured by reference
// Capture all by value [=] or all by reference [&]
auto print_all = [&]() { // captures everything by reference
for (int n : nums) std::cout << n << ' ';
std::cout << '\n';
};
print_all();
// Init capture (C++14) — create new variables in the capture
auto counter = [count = 0]() mutable { return ++count; };
std::cout << counter() << '\n'; // 1
std::cout << counter() << '\n'; // 2
std::cout << counter() << '\n'; // 3
}泛型和模板 Lambda
C++14 引入了使用 auto 参数的泛型 Lambda。C++20 进一步扩展了显式模板参数列表,使 Lambda 具有完整的模板能力。
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <type_traits>
int main() {
// C++14: generic lambda — auto parameters
auto print = [](const auto& x) {
std::cout << x << '\n';
};
print(42); // int
print(3.14); // double
print("hello"); // const char*
// C++14: generic lambda for comparisons
auto max_of = [](const auto& a, const auto& b) {
return (a > b) ? a : b;
};
std::cout << max_of(3, 7) << '\n'; // 7
std::cout << max_of("abc", "xyz") << '\n'; // xyz (lexicographic)
// C++20: template lambda — explicit template parameters
auto add = []<typename T>(T a, T b) -> T {
return a + b;
};
std::cout << add(1, 2) << '\n'; // 3
std::cout << add(1.5, 2.5) << '\n'; // 4.0
// C++20: constrained template lambda with concepts
auto integral_only = []<std::integral T>(T x) {
return x * 2;
};
std::cout << integral_only(5) << '\n'; // 10
// integral_only(3.14); // ERROR: double doesn't satisfy std::integral
// Using generic lambdas with algorithms
std::vector<std::string> words = {"banana", "apple", "cherry"};
std::sort(words.begin(), words.end(),
[](const auto& a, const auto& b) {
return a.size() < b.size(); // sort by length
});
for (const auto& w : words) std::cout << w << ' '; // apple banana cherry
std::cout << '\n';
}std::function 和 std::invoke
std::function 是一个类型擦除的可调用包装器,可以持有任何具有匹配签名的可调用对象──Lambda、函数指针、函数对象、成员函数指针。std::invoke 是调用任何可调用对象的统一方式。
#include <functional>
#include <iostream>
#include <string>
#include <vector>
// Regular function
int add(int a, int b) { return a + b; }
struct Multiplier {
int factor;
int operator()(int x) const { return x * factor; }
int multiply(int x) const { return x * factor; }
};
int main() {
// std::function can hold different callable types
std::function<int(int, int)> op;
op = add; // function pointer
std::cout << op(3, 4) << '\n'; // 7
op = [](int a, int b) { return a * b; }; // lambda
std::cout << op(3, 4) << '\n'; // 12
// Store callables in a collection
std::vector<std::function<int(int)>> transforms;
transforms.push_back([](int x) { return x + 1; });
transforms.push_back([](int x) { return x * 2; });
transforms.push_back(Multiplier{10}); // functor
int val = 5;
for (auto& fn : transforms) {
val = fn(val);
}
std::cout << val << '\n'; // ((5+1)*2)*10 = 120
// std::invoke — uniform call syntax for any callable
Multiplier m{3};
std::cout << std::invoke(add, 10, 20) << '\n'; // 30
std::cout << std::invoke(m, 7) << '\n'; // 21
std::cout << std::invoke(&Multiplier::multiply, m, 7) << '\n'; // 21
std::cout << std::invoke(&Multiplier::factor, m) << '\n'; // 3 (data member)
}IIFE 模式
IIFE(立即调用函数表达式)是一种借鉴自 JavaScript 的模式。你定义一个 Lambda 并立即调用它。这对于 const 变量的复杂初始化非常有用:
const auto config = [&]() {
Config c;
c.host = read_env("HOST");
c.port = parse_int(read_env("PORT"));
if (c.port <= 0) c.port = 8080;
c.debug = is_debug_build();
return c;
}(); // <-- note the () — invoked immediately没有 IIFE,你要么需要一个非 const 变量(失去不可变性保证),要么需要一个单独的工厂函数。IIFE 使初始化逻辑保持局部性,且结果为 const。
当 Lambda 比被捕获的变量存活更久时,按引用捕获是危险的:
std::function<int()> make_counter() {
int count = 0;
return [&count]() { return ++count; }; // BUG: count is destroyed!
}返回的 Lambda 持有对一个已不存在的局部变量的引用。这是未定义行为。通过按值捕获(需要时加 mutable)来修复:
std::function<int()> make_counter() {
int count = 0;
return [count]() mutable { return ++count; }; // OK: owns a copy
}经验法则:对短生命周期的 Lambda(算法谓词)使用引用捕获,对长生命周期的 Lambda(回调、存储在数据结构中的)使用值捕获。
std::function 有开销:它会分配内存(对于大的闭包)、使用虚派发,并阻止内联。对于算法谓词和其他立即使用可调用对象的场景,直接传递 Lambda──编译器可以完全内联它。
仅在以下情况使用 std::function:
- 存储可调用对象以备后用
- 类型擦除不同的可调用类型到统一容器中
- 跨 ABI 边界传递可调用对象
在模板中,使用 auto 或概念接受可调用对象,无需类型擦除:
template<std::invocable<int> F>
void apply(F&& fn, int value) {
fn(value); // no std::function overhead
}- Lambda 是零开销的匿名函数对象──可以自由地与 STL 算法配合使用
- 对长生命周期的 Lambda 使用按值捕获
[x]更安全;对短生命周期的使用按引用捕获[&x] - C++14 泛型 Lambda(
auto参数)和 C++20 模板 Lambda 提供完整的泛型编程能力 std::function对可调用对象进行类型擦除但有开销──尽可能使用auto或模板- IIFE 模式
[&]() { ... }()允许对const变量进行复杂初始化 - 初始化捕获(
[x = expr])允许在闭包中创建新变量(C++14)
测验 — 检验你的知识
(15 经验值)1. `mutable` 关键字对 Lambda 有什么作用?
2. 在一个按引用捕获局部变量的 Lambda 从函数返回时,有什么危险?
3. `[count = 0]() mutable { return ++count; }` 演示了什么?