C++ Lambda 表达式
Lambda 表达式的完整语法如下:
[capture](parameters) mutable -> return_type { body }
[capture]
(捕获列表):指定外部变量如何被 Lambda 表达式捕获(按值或按引用)。
(parameters)
(参数列表):类似普通函数的参数,定义 Lambda 接受的输入。
mutable
(可选):允许在按值捕获时修改捕获的变量(默认按值捕获是只读的)。
-> return_type
(返回类型,可选):显式指定返回类型,通常由编译器推导。
{ body }
(函数体):Lambda 的实现逻辑。
捕获列表决定了 Lambda 如何访问外部作用域的变量。捕获方式有以下几种:
1.按值捕获 [x]
外部变量被复制到 Lambda 内部,Lambda 持有该变量的副本。
int x = 10;
auto func = [x]() { return x * 2; }; // x 是副本
std::cout << func() << "\n"; // 输出 20
x = 20;
std::cout << func() << "\n"; // 依然输出 20,因为 func 内部的 x 是副本
按值捕获默认只读,使用 mutable
允许修改副本,但不会影响外部变量。
int x = 10;
auto func = [x]() mutable { x += 1; return x; };
std::cout << func() << "\n"; // 输出 11
std::cout << x << "\n"; // 输出 10,外部 x 不变
2.按引用捕获 [&x]
Lambda 直接引用外部变量,修改 Lambda 内部的变量会影响外部变量。如果外部变量被销毁(例如离开作用域),Lambda 引用它会导致未定义行为(悬垂引用)。
示例:
int x = 10;
auto refFunc = [&x]() { return x * 2; }; // x 是引用
x = 20;
std::cout << refFunc() << "\n"; // 输出 40,因为 refFunc 引用了修改后的 x
3.全局捕获
[=]
:按值捕获所有外部变量的副本。
[&]
:按引用捕获所有外部变量。
混合捕获:可以组合,例如 [=, &x]
表示默认按值捕获,但 x
按引用捕获。
示例:
int x = 10, y = 5;
auto mixed = [=, &x]() { return x + y; }; // y 按值,x 按引用
x = 20;
std::cout << mixed() << "\n"; // 输出 25(x=20, y=5 的副本)
4.捕获 this
在类成员函数中,[this]
捕获当前对象的指针,[*this]
(C++17 起)捕获当前对象的副本。按引用捕获 [&]
隐式包含 this
。
示例:
struct Example {int x = 10;void func() {auto lambda = [this]() { return x * 2; };std::cout << lambda() << "\n"; // 输出 20}
};
5.空捕获 []
示例:
auto callback = [](int x) { std::cout << "Callback: " << x << "\n"; };
Lambda 表达式的捕获列表是 [],表示空捕获,即不捕获任何外部变量,既不是按值捕获也不是按引用捕获。[] 表示 Lambda 表达式不从外部作用域捕获任何变量。Lambda 的函数体 { std::cout << "Callback: " << x << "\n"; }
只使用了参数 x(通过函数调用传入)和全局对象 std::cout
。std::cout
是全局的,不需要捕获,而 x 是 Lambda 的参数,不是外部作用域的变量。
捕获(按值 [=] 或按引用 [&])只有在 Lambda 访问外部作用域的变量时才起作用。例如,如果 Lambda 使用了外部的 int y
,才会涉及捕获方式。
Lambda 表达式的实现原理:
Lambda 表达式实际上是编译器生成的匿名类的实例(称为闭包对象)。例如:
auto func = [x]() { return x * 2; };
编译器会生成类似以下的类:
class Lambda {int x; // 捕获的变量
public:Lambda(int x_) : x(x_) {}int operator()() const { return x * 2; } // 重载函数调用操作符,operator() 是重载的函数调用操作符,() 表示这个操作符不接受参数(空参数列表),int 是返回值类型,表示调用这个操作符会返回一个整数,const 表示这个成员函数不会修改对象的状态(x 不会被改变)。
};
调用 func()
实际上是调用这个类的 operator()
。这解释了 Lambda 为什么可以像函数一样使用。
Lambda 的常见用途:
1.标准库算法:与 <algorithm>
配合,例如 std::sort
、std::for_each
。
std::vector<int> vec = {3, 1, 4, 1, 5};
std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });
2.异步编程:与 std::async
或线程配合。
auto task = []() { std::cout << "Running task\n"; };
std::async(std::launch::async, task);
3.回调函数:传递给需要回调的函数。
auto callback = [](int x) { std::cout << "Callback: " << x << "\n"; };
someFunction(callback);
4.立即执行(IIFE,Immediately Invoked Function Expression):
int result = []() { return 42; }(); // 立即调用,result = 42
按引用捕获时,确保捕获的变量在 Lambda 使用时仍然有效。错误用法:
auto createLambda() {int x = 10;return [&x]() { return x; }; // 悬垂引用,x 在函数返回后销毁
}
按值捕获会复制变量,可能会增加内存开销。对于大对象,考虑按引用捕获或使用 std::move
(C++11 起支持移动捕获,C++14 增强)。
C++14 支持泛型 Lambda(auto
参数)和初始化捕获。
auto lambda = [y = 10](auto x) { return x + y; }; // 初始化捕获
在类成员函数中,Lambda 表达式可以通过 [this]
捕获当前对象的指针,或者通过 [&]
隐式捕获 this
。然而,这会导致 Lambda 持有指向对象的引用,如果对象在 Lambda 调用时被销毁,会导致未定义行为(悬垂指针)。C++17 引入了 [*this]
,允许 Lambda 按值捕获当前对象的副本,从而避免悬垂引用问题。适合需要延长对象生命周期的场景,例如异步回调或线程中。
示例:
#include <iostream>
#include <functional>struct Example {int x = 10;void createLambda() {// 使用 [*this] 捕获对象副本auto lambda = [*this]() {std::cout << "Lambda: x = " << x << "\n";// 修改 x 不影响原始对象x = 20;std::cout << "Lambda modified: x = " << x << "\n";};// 调用 Lambdalambda();// 原始对象的 x 未改变std::cout << "Original: x = " << x << "\n";}// 模拟异步回调std::function<void()> createAsyncCallback() {// 返回 Lambda,捕获 [*this]return [*this]() {std::cout << "Async callback: x = " << x << "\n";};}
};int main() {// 测试 [*this] 捕获Example obj;obj.createLambda();// 测试异步场景auto callback = obj.createAsyncCallback();// obj 销毁后,callback 仍然有效,因为它持有 obj 的副本callback();return 0;
}
输出:
Lambda: x = 10
Lambda modified: x = 20
Original: x = 10
Async callback: x = 10
在 C++11 到 C++17 中,Lambda 表达式生成的闭包类型没有默认构造函数,且不能直接赋值(即使 Lambda 是无状态的,即不捕获任何变量)。这限制了 Lambda 在某些场景下的使用,例如存储在需要默认构造或赋值的容器中。C++20 放宽了这一限制,允许无状态 Lambda(不捕获任何变量的 Lambda)具有默认构造函数和赋值操作符。便于在容器(如 std::vector
)、可选类型(如 std::optional
)或需要默认构造的场景中使用。
特点:
1.无状态 Lambda:指 Lambda 表达式不捕获任何变量([]
为空,且函数体不依赖外部变量)。
2.默认构造:C++20 允许无状态 Lambda 的闭包类型有默认构造函数,可以创建未初始化的闭包对象。
3.赋值:无状态 Lambda 可以相互赋值,因为它们的行为完全由代码定义,不依赖捕获的状态。
示例:
#include <iostream>
#include <vector>
#include <optional>int main() {// 定义一个无状态 Lambda,lambda 不捕获任何变量([]),因此是无状态的,行为完全由函数体 { return 42; } 定义。auto lambda = []() { return 42; };// 默认构造无状态 Lambda(C++20),decltype(lambda) defaultLambda; 创建一个默认构造的闭包对象,行为与 lambda 相同。decltype(lambda) defaultLambda; // 默认构造std::cout << "Default constructed Lambda: " << defaultLambda() << "\n";// 赋值(C++20),assignedLambda = lambda; 将 lambda 的行为复制到 assignedLambda,这是 C++20 新增的功能。decltype(lambda) assignedLambda;assignedLambda = lambda; // 赋值操作std::cout << "Assigned Lambda: " << assignedLambda() << "\n";// 存储在容器中,std::vector 和 std::optional 可以存储无状态 Lambda,因为它们支持默认构造和赋值。std::vector<decltype(lambda)> lambdaVector(3); // 默认构造 3 个 Lambdafor (const auto& l : lambdaVector) {std::cout << "Vector Lambda: " << l() << "\n";}// 使用 std::optionalstd::optional<decltype(lambda)> optionalLambda;optionalLambda = lambda; // 赋值if (optionalLambda) {std::cout << "Optional Lambda: " << optionalLambda.value()() << "\n";}return 0;
}
输出:
Default constructed Lambda: 42
Assigned Lambda: 42
Vector Lambda: 42
Vector Lambda: 42
Vector Lambda: 42
Optional Lambda: 42
如果 Lambda 捕获变量(有状态 Lambda),则无法使用默认构造或赋值,因为它们的行为依赖捕获的变量。
int x = 10;
auto statefulLambda = [x]() { return x; };
decltype(statefulLambda) defaultLambda; // 错误:无默认构造函数
statefulLambda = statefulLambda; // 错误:无赋值操作符