C++ Lambda表达式深度解析
一、Lambda基础
1.1 什么是Lambda
Lambda表达式是C++11引入的匿名函数,用于就地定义函数对象。
// 传统函数对象
struct Add {
int operator()(int a, int b) {
return a + b;
}
};
Add add;
int result1 = add(3, 4); // 7
// Lambda表达式
auto addLambda = [](int a, int b) {
return a + b;
};
int result2 = addLambda(3, 4); // 7
1.2 Lambda语法
[capture](parameters) -> return_type {
// 函数体
}
各部分说明
// 完整语法示例
auto lambda = [capture](int a, int b) -> int {
return a + b;
};
// 简化语法(自动推导返回类型)
auto lambda2 = [](int a, int b) {
return a + b;
};
1.3 基本示例
// 无参数
auto sayHello = []() {
std::cout << "Hello!" << std::endl;
};
// 有参数
auto add = [](int a, int b) {
return a + b;
};
// 使用
sayHello();
int result = add(3, 5); // 8
二、捕获机制
2.1 捕获方式
| 捕获方式 | 语法 | 说明 |
|---|---|---|
| 无捕获 | [] | 不捕获外部变量 |
| 按值捕获 | [x, y] | 复制变量 |
| 按引用捕获 | [&x, &y] | 引用变量 |
| 混合捕获 | [x, &y] | 混合方式 |
| 全部按值 | [=] | 所有变量按值 |
| 全部按引用 | [&] | 所有变量按引用 |
| 初始化捕获 | [x = expr] | C++14 |
2.2 按值捕获
int x = 10;
int y = 20;
// 按值捕获
auto lambda1 = [x, y](int z) {
// x和y是副本,不能修改
return x + y + z;
};
int result = lambda1(5); // 35
// x仍然是10,y仍然是20
2.3 按引用捕获
int x = 10;
int y = 20;
// 按引用捕获
auto lambda2 = [&x, &y]() {
x += 5; // 修改外部变量
y += 5;
};
lambda2();
// x = 15, y = 25
2.4 混合捕获
int x = 10;
int y = 20;
int z = 30;
// x按值,y按引用
auto lambda = [x, &y](int a) {
return x + y + a; // x只读,y可修改
};
2.5 默认捕获
int x = 10;
int y = 20;
// 全部按值
auto lambda1 = [=]() {
return x + y; // x和y是副本
};
// 全部按引用
auto lambda2 = [&]() {
x++; // 可以修改
y++;
return x + y;
};
2.6 mutable关键字
int x = 10;
// 默认:按值捕获的变量是const的
auto lambda1 = [x]() {
// x++; // 编译错误
return x;
};
// 使用mutable:可以修改按值捕获的副本
auto lambda2 = [x]() mutable {
x++; // 修改副本,不影响外部x
return x;
};
int result1 = lambda2(); // 11
int result2 = lambda2(); // 12
// 外部x仍然是10
三、返回类型
3.1 自动推导
// 自动推导返回类型
auto lambda1 = [](int a, int b) {
return a + b; // 推导为int
};
auto lambda2 = [](int a, int b) {
if (a > b) return a;
else return b; // 推导为int
};
3.2 指定返回类型
// 显式指定返回类型
auto lambda = [](int a, int b) -> int {
if (a > b) return a;
return b;
};
// 复杂返回类型
auto lambda2 = [](int x) -> std::string {
return "Value: " + std::to_string(x);
};
3.3 返回引用
// 返回引用
int x = 10;
int y = 20;
auto& ref = [&x, &y](bool select) -> int& {
return select ? x : y;
};
ref(true) = 100; // x = 100
四、C++14特性
4.1 泛型Lambda
// 泛型Lambda(使用auto参数)
auto print = [](auto value) {
std::cout << value << std::endl;
};
print(42); // int
print(3.14); // double
print("hello"); // const char*
print(std::string("world")); // std::string
// 多个auto参数
auto add = [](auto a, auto b) {
return a + b;
};
int sum1 = add(1, 2);
double sum2 = add(1.5, 2.5);
std::string sum3 = add(std::string("Hello "), std::string("World"));
4.2 初始化捕获
// C++11:需要在捕获列表中声明
int x = 10;
auto lambda1 = [x] {
return x * 2;
};
// C++14:可以在捕获时初始化
auto lambda2 = [value = 10] {
return value * 2;
};
// 移动语义
auto ptr = std::make_unique<int>(42);
auto lambda3 = [p = std::move(ptr)] {
return *p;
};
// 计算捕获
int multiplier = 2;
auto lambda4 = [result = multiplier * 3] {
return result;
}; // result = 6
// 引用捕获并初始化
int x = 10;
auto lambda5 = [&r = x] {
r = 20;
};
lambda5(); // x = 20
4.3 constexpr Lambda
// C++17:constexpr Lambda
constexpr auto square = [](int x) {
return x * x;
};
// 编译期计算
constexpr int result = square(5); // 25
五、实际应用
5.1 STL算法
#include <algorithm>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// for_each
std::for_each(v.begin(), v.end(), [](int& x) {
x *= 2;
}); // v = {2, 4, 6, 8, 10}
// find_if
auto it = std::find_if(v.begin(), v.end(), [](int x) {
return x > 5;
}); // 找到第一个大于5的元素
// sort
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 降序排序
});
// accumulate
int sum = std::accumulate(v.begin(), v.end(), 0,
[](int acc, int x) {
return acc + x;
});
5.2 自定义比较
struct Person {
std::string name;
int age;
};
std::vector<Person> people = {
{"Alice", 25},
{"Bob", 20},
{"Charlie", 30}
};
// 按年龄排序
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});
// 按名字排序
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.name < b.name;
});
5.3 事件处理
class Button {
public:
using ClickHandler = std::function<void()>;
void setOnClick(ClickHandler handler) {
clickHandler = handler;
}
void click() {
if (clickHandler) {
clickHandler();
}
}
private:
ClickHandler clickHandler;
};
// 使用Lambda处理点击
Button button;
button.setOnClick([]() {
std::cout << "Button clicked!" << std::endl;
});
button.click();
5.4 线程
#include <thread>
// 使用Lambda创建线程
int x = 10;
std::thread t([x]() mutable {
x *= 2;
std::cout << "Thread: " << x << std::endl;
});
t.join();
std::cout << "Main: " << x << std::endl;
六、高级用法
6.1 Lambda作为函数参数
#include <functional>
// 接受Lambda作为参数
void process(std::function<int(int, int)> func) {
int result = func(10, 20);
std::cout << "Result: " << result << std::endl;
}
// 使用
process([](int a, int b) {
return a + b;
});
process([](int a, int b) {
return a * b;
});
6.2 Lambda递归
// 使用std::function实现递归
std::function<int(int)> factorial = [&](int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
};
int result = factorial(5); // 120
6.3 Lambda捕获Lambda
// 捕获另一个Lambda
auto logger = [](const std::string& msg) {
std::cout << "[LOG] " << msg << std::endl;
};
auto process = [logger](const std::string& data) {
logger("Processing: " + data);
// 处理逻辑
logger("Done");
};
process("some data");
6.4 延迟执行
// 延迟执行
auto delayedLog = []() {
std::cout << "This will be executed later" << std::endl;
};
// 保存Lambda
std::vector<std::function<void()>> tasks;
tasks.push_back(delayedLog);
// 执行所有任务
for (auto& task : tasks) {
task();
}
七、性能考虑
7.1 Lambda与函数对象
// Lambda
auto lambda = [](int x) { return x * 2; };
// 等价函数对象
struct Functor {
int operator()(int x) const {
return x * 2;
}
};
// 编译器通常生成相同代码
// 性能相当
7.2 捕获开销
int x = 10;
// 按值捕获:复制开销
auto lambda1 = [x](int y) {
return x + y;
};
// 按引用捕获:无额外开销
auto lambda2 = [&x](int y) {
return x + y;
};
// 建议:大对象按引用捕获
std::vector<int> data(1000);
auto lambda3 = [&data](int index) {
return data[index];
};
7.3 std::function开销
// std::function有额外开销
std::function<int(int)> func = [](int x) { return x * 2; };
// 建议:使用auto避免开销
auto lambda = [](int x) { return x * 2; };
八、最佳实践
8.1 选择捕获方式
// ✅ 小对象按值捕获
int x = 10;
auto lambda1 = [x](int y) {
return x + y;
};
// ✅ 大对象按引用捕获
std::vector<int> data;
auto lambda2 = [&data](int index) {
return data[index];
};
// ✅ 需要修改时按引用捕获
int count = 0;
auto lambda3 = [&count]() {
count++;
};
// ⚠️ 注意悬空引用
// ❌ 错误:返回的Lambda引用了局部变量
auto getLambda() {
int x = 10;
return [&x]() { return x; }; // 危险!
}
// ✅ 正确:按值捕获
auto getLambda2() {
int x = 10;
return [x]() { return x; };
}
8.2 使用auto
// ✅ 推荐:使用auto
auto lambda = [](int x) { return x * 2; };
// ❌ 不推荐:显式类型
std::function<int(int)> lambda2 = [](int x) { return x * 2; };
8.3 简化代码
// ✅ 简洁的Lambda
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; });
// ❌ 冗长的命名函数对象
struct Compare {
bool operator()(int a, int b) const {
return a > b;
}
};
std::sort(v.begin(), v.end(), Compare());
九、常见陷阱
9.1 悬空引用
// ❌ 危险:Lambda引用了销毁的变量
std::function<int()> getBadLambda() {
int x = 10;
return [&x] { return x; }; // x已销毁
}
auto lambda = getBadLambda();
// lambda(); // 未定义行为
9.2 捕获this
class MyClass {
int value = 10;
public:
// ❌ 危险:Lambda可能比对象活得更久
std::function<int()> getLambda1() {
return [this] { return value; };
}
// ✅ 安全:捕获副本
std::function<int()> getLambda2() {
return [v = value] { return v; };
}
// ✅ 或者使用shared_from_this
};
9.3 mutable误用
int x = 10;
// ❌ 误用mutable:以为能修改外部变量
auto lambda = [x]() mutable {
x++; // 只修改副本,不影响外部x
};
lambda();
// x仍然是10
// ✅ 正确:使用引用捕获
auto lambda2 = [&x]() {
x++; // 修改外部x
};
十、总结
10.1 核心概念
| 概念 | 说明 |
|---|---|
| 捕获 | [ ] 控制外部变量访问 |
| 参数 | ( ) 定义参数列表 |
| 返回类型 | -> type 或自动推导 |
| mutable | 允许修改按值捕获的副本 |
| 泛型 | C++14使用auto参数 |
10.2 选择指南
需要函数对象?
↓
简单且短期使用?
├─ 是 → Lambda
└─ 否 → 命名函数对象
捕获变量?
↓
需要修改外部变量?
├─ 是 → 按引用捕获
└─ 否 → 按值捕获(小对象)/ 按引用捕获(大对象)
10.3 最佳实践
- ✅ 简单场景使用Lambda
- ✅ 复杂场景使用命名函数对象
- ✅ 小对象按值捕获
- ✅ 大对象按引用捕获
- ✅ 需要修改时按引用捕获
- ✅ 使用auto类型
- ⚠️ 注意悬空引用
- ⚠️ 注意对象生命周期
- ❌ 不要过度复杂化