c++中 Lambda表达式
Lambda优化技巧
尽量使用值捕获简单类型
避免捕获大型对象(使用引用或智能指针)
将不修改的捕获标记为const
使用初始化捕获移动语义资源
前言
1. Lambda表达式基本语法
[捕获列表](参数列表) mutable(可选) 异常属性(可选) -> 返回类型(可选) {// 函数体
}
捕获列表决定了lambda表达式可以访问哪些外部变量以及如何访问它们:
-
[]
:不捕获任何外部变量[=]
:以值捕获所有外部变量 -
[&]
:以引用捕获所有外部变量[var]
:以值捕获特定变量var -
[&var]
:以引用捕获特定变量var[=, &var]
:默认以值捕获,但var以引用捕获 -
[&, var]
:默认以引用捕获,但var以值捕获[this]
:捕获当前类的this指针
int a = 1, b = 2, c = 3;auto lambda1 = [=]() { return a + b; }; // 值捕获a和b
auto lambda2 = [&]() { c = a + b; }; // 引用捕获a、b、c
auto lambda3 = [a, &b]() { b = a + 10; }; // 值捕获a,引用捕获b
返回类型推断
当lambda体只有一条return语句时,返回类型可以自动推断:
auto lambda = [](int x) { return x * x; }; // 返回类型推断为int
复杂情况下需要显式指定返回类型:
auto lambda = [](int x) -> double {if (x > 0) return 1.0 / x;else return x * x;
};
通用Lambda (C++14)
C++14引入了通用lambda,可以使用auto参数:
auto print = [](auto x) { cout << x << endl; };
print(123); // 输出123
print("hello"); // 输出hello
可变Lambda (mutable)
int counter = 0;// 没有mutable会编译错误
auto incrementer = [counter]() mutable {return ++counter; // 修改的是副本
};incrementer(); // 1
incrementer(); // 2
cout << counter; // 仍然是0
编译错误:默认 lambda 的 operator()
是 const
的,不能修改按值捕获的变量。修复方式:加 mutable
或改用引用捕获 [&counter]
该 lambda 会被转换为类似以下的类:
class __Lambda_Counter {
public:__Lambda_Counter(int counter) : counter(counter) {} // 拷贝构造// mutable 移除了 operator() 的 const 限定int operator()() {return ++counter; // 允许修改成员变量}private:int counter; // 按值捕获的副本
};__Lambda_Counter incrementer(counter); // 创建闭包对象
2. Lambda表达式的完整生命周期
Lambda表达式实际上是一个编译器生成的匿名类实例,理解这一点对掌握Lambda至关重要。
int x = 10;
auto lambda = [x](int y) mutable {x += y;return x;
};// 编译器生成的等价类
class __Lambda_10 {
public:__Lambda_10(int x) : x(x) {}int operator()(int y) {x += y;return x;}private:int x;
};__Lambda_10 lambda(5);
mutable
的作用:
- 默认情况下,lambda 的
operator()
是const
的,不能修改捕获的变量加上mutable
后,operator()
变为非const
,允许修改按值捕获的变量(但不会影响外部的原始变量)。 - 捕获方式:如果改成
[&x]
,则捕获引用,修改会影响外部的x
。[x]
是按值捕获,lambda
内部存储的是x
的副本。调用方式:lambda(5)
实际上是用lambda.operator()(5)
,就像调用普通函数一样。
2. 捕获方式的深层细节
(1) 值捕获的陷阱
vector<int> data{1, 2, 3};// 看似捕获了data,实则捕获的是data的拷贝
auto lambda = [data]() {// 这里操作的是data的副本for(auto& x : data) x *= 2;
};// 原始data未被修改
lambda();
for(auto x : data) cout << x << " "; // 输出: 1 2 3
按值捕获 [data]
:Lambda 内部会生成一个 data
的完整拷贝(调用 vector
的拷贝构造函数)。修改的是副本:x *= 2
操作的是 lambda 内部的副本,不影响外部的 data
。
你的 lambda 会被编译器转换为类似下面的类:
class __Lambda_Data {
public:__Lambda_Data(const vector<int>& data) : data(data) {} // 拷贝构造void operator()() {for(auto& x : data) x *= 2; // 修改的是成员变量 data}private:vector<int> data; // 按值捕获的副本
};__Lambda_Data lambda(data); // 调用时拷贝 data
lambda(); // 修改的是内部的 data
这样的就使按值捕获都会在lambda类里面拷贝一份副样本这样的话会导致里面实现的函数所得到的值不会改变原来的参数只会改变你拷贝构造的那一份数据
如果需要修改外部 data
,需使用 引用捕获 [&data]
(2) 引用捕获的生命周期风险
auto createLambda() {int local = 42;return [&local]() { return local; }; // 危险!返回后local被销毁
}auto badLambda = createLambda();
cout << badLambda(); // 未定义行为,可能崩溃或输出垃圾值
local
的生命周期:仅在 createLambda()
函数执行期间有效Lambda 行为:捕获了 local
的引用,但该引用在函数返回后失效。
(3) 初始化捕获 (C++14)
auto ptr = make_unique<int>(10);// C++14引入的初始化捕获
auto lambda = [p = move(ptr)]() {return *p;
};// ptr已被转移所有权,现在为nullptr
4. 模板Lambda (C++20)
C++20引入了模板参数支持:
auto genericLambda = []<typename T>(T x, T y) {return x + y;
};cout << genericLambda(1, 2); // 3
cout << genericLambda(1.5, 2.5); // 4.0
这样的话就方便函数的打印可以用来直接打印函数
std::vector<int> vi{1, 2, 3};
std::vector<double> vd{1.1, 2.2};
auto print = []<typename T>(const std::vector<T>& v) {for (const auto& x : v) std::cout << x << " ";
};
print(vi); // 1 2 3
print(vd); // 1.1 2.2
常见Lambda错误
-
悬空引用:Lambda生命周期长于捕获的引用
-
意外拷贝:捕获大型对象未使用引用
-
mutable遗漏:需要修改值捕获变量时
-
类型不匹配:返回类型推断错误
总结
-
默认选择Lambda:除非需要参数重排/部分绑定(此时用bind)
-
显式优于隐式:避免
[=]/[&]
全捕获,明确列出所需变量 -
复杂度控制:超过5行的逻辑考虑提取为命名函数
-
线程安全:多线程共享Lambda时避免可变共享状态
-
结合现代特性:与auto/constexpr/concept等特性协同使用
Lambda表达式重新定义了C++的函数式编程范式,合理运用可使代码既保持高性能又提升可读性,是现代C++开发的核心技能之一。