目录
- C++ Lambda表达式捕获机制的详细原理分析
- Lambda的本质机制
- 关键实现细节
- 特殊捕获方式
- [ ]空捕获
- [&]全引用捕获
- [=]全值(拷贝)捕获
- 混合捕获
- [=,&variable]拷贝及部分引用捕获
- [&,variable]引用及部分拷贝捕获
- 显式捕获
- [variable]拷贝捕获部分变量
- [&variable]引用捕获部分变量
- [this]拷贝捕获this
C++ Lambda表达式捕获机制的详细原理分析
Lambda的本质机制
Lambda表达式本质是编译器生成的匿名类,通过重载operator()实现函数调用运算符。捕获列表中的变量会被转换为该类的成员变量:
// 原始Lambda
auto f = [x](){ return x+1; };// 等效编译器生成类
class __lambda_XXXX {int x; // 捕获变量存储为成员变量
public:int operator()() const { return x+1; }
};
关键实现细节
-
捕获时机:发生在
lambda
表达式定义时,后续外部变量修改不影响已捕获的值(引用捕获除外) -
成员初始化
__lambda_XXXX{var} // 值捕获时调用拷贝构造 __lambda_XXXX{std::move(var)} // C++14支持移动捕获
-
生命周期问题:引用捕获必须确保外部变量生命周期长于lambda对象
-
性能影响
- 值捕获:可能引起拷贝开销
- 引用捕获:无拷贝开销,但有悬空引用风险
特殊捕获方式
-
初始化捕获(C++14)
[ptr = std::move(unique_ptr)]{} // 移动语义捕获
-
结构化绑定捕获(C++17):
auto [a, b] = getPair(); auto f = [a, b](){...};
[ ] 不截取任何变量
[&]截取外部作用域中所有变量,并作为引用在函数体中使用
[=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
[=,&variable] 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对以逗号分隔variable使用引用
[&,variable] 以引用的方式捕获外部作用域中所有变量,对以逗号分隔的变量列表variable使用值的方式捕获
[variable] 对以逗号分隔的变量列表variable使用值的方式捕获
[&variable] 对以逗号分隔的变量列表variable使用引用的方式捕获
[this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。
cppinsights将高级C++代码转换为其等效的低级代码表示;
[ ]空捕获
- 原理:不生成任何成员变量,相当于没有状态的函数对象
- 限制:只能访问全局变量和静态变量
[&]全引用捕获
-
原理:所有捕获变量存储为引用类型成员
-
底层实现:
class __lambda_7_11 {std::string& s; // 引用类型成员int& a; // 引用类型成员 public:void operator()() const {s += std::to_string(a);a += 100; // 直接修改原变量} };
-
特性:修改会影响外部变量,需注意生命周期问题
源:
#include <string>
#include <iostream>
int main()
{int a = 10;std::string s = "hello";auto f = [&](){s += std::to_string(a);a += 100;};std::cout << a << std::endl;f();return 0;
}
cppinsights展开:
#include <string>
#include <iostream>
int main()
{int a = 10;std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());class __lambda_7_11{public:inline /*constexpr */ void operator()() const{s.operator+=(std::to_string(a));a = a + 100;}private:std::basic_string<char>& s;int& a;public:__lambda_7_11(std::basic_string<char>& _s, int& _a): s{ _s }, a{ _a }{}}; // 函数对象__lambda_7_11 f = __lambda_7_11{ s, a }; // 构造函数std::cout.operator<<(a).operator<<(std::endl); // 输出f.operator()(); // 调用函数return 0;
}
[=]全值(拷贝)捕获
-
原理:所有变量存储为值类型成员
-
底层实现:
class __lambda_9_11 {std::string s; // 值类型成员int a; // 值类型成员 public:void operator()() const { // 默认const修饰std::cout << s << a; // 只能读取不能修改} };
-
mutable修饰:移除operator()的const限定,允许修改副本
mutable lambda允许: void operator()() { // 非const版本a += 100; // 修改副本值 }
源:
#include <string>
#include <iostream>int main()
{int a = 10;std::string s = "hello";// 默认是常函数void operator()() constauto f = [=]() {std::cout << "Lambda = " << s << std::endl;std::cout << "Lambda = " << a << std::endl;// 报错:表达式必须是可修改的左值// a += 100;};// 捕获时机:发生在lambda表达式定义时,后续外部变量修改不影响已捕获的值(引用捕获除外)a = 100;std::cout << "a = " << a << std::endl;f();// 设置mutable,使得void operator()()可以修改捕获的变量的副本(非本身)auto f2 = [=]()mutable {std::cout << "Lambda = " << s << std::endl;std::cout << "Lambda = " << a << std::endl;// 这里的a是外部变量的副本s += std::to_string(a); // 无法改变外部变量sa += 100; // 可以改变外部变量astd::cout << "Lambda = " << s << std::endl;std::cout << "Lambda = " << a << std::endl;};f2();// 没有改变外部变量std::cout << "s = " << s << std::endl;// s = hello std::cout << "a = " << a << std::endl;// a = 10return 0;
}
/*
a = 100
Lambda = hello
Lambda = 10
Lambda = hello
Lambda = 100
Lambda = hello100
Lambda = 200
s = hello
a = 100
*/
cppinsights展开:
#include <string>
#include <iostream>int main()
{int a = 10;std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());class __lambda_9_11{public:inline /*constexpr */ void operator()() const{std::operator<<(std::operator<<(std::cout, "Lambda = "), s).operator<<(std::endl);std::operator<<(std::cout, "Lambda = ").operator<<(a).operator<<(std::endl);}private:std::basic_string<char> s;int a;public:// inline __lambda_9_11 & operator=(const __lambda_9_11 &) /* noexcept */ = delete;// inline ~__lambda_9_11() noexcept = default;__lambda_9_11(const std::basic_string<char>& _s, int& _a): s{ _s }, a{ _a }{}};__lambda_9_11 f = __lambda_9_11{ s, a };// 捕获时机:发生在lambda表达式定义时,后续外部变量修改不影响已捕获的值(引用捕获除外)a = 100;std::operator<<(std::cout, "a = ").operator<<(a).operator<<(std::endl);f.operator()();class __lambda_22_12{public:inline /*constexpr */ void operator()(){std::operator<<(std::operator<<(std::cout, "Lambda = "), s).operator<<(std::endl);std::operator<<(std::cout, "Lambda = ").operator<<(a).operator<<(std::endl);s.operator+=(std::to_string(a));a = a + 100;std::operator<<(std::operator<<(std::cout, "Lambda = "), s).operator<<(std::endl);std::operator<<(std::cout, "Lambda = ").operator<<(a).operator<<(std::endl);}private:std::basic_string<char> s;int a;public:// inline __lambda_22_12 & operator=(const __lambda_22_12 &) /* noexcept */ = delete;// inline ~__lambda_22_12() noexcept = default;__lambda_22_12(const std::basic_string<char>& _s, int& _a): s{ _s }, a{ _a }{}};__lambda_22_12 f2 = __lambda_22_12{ s, a };f2.operator()();std::operator<<(std::operator<<(std::cout, "s = "), s).operator<<(std::endl);std::operator<<(std::cout, "a = ").operator<<(a).operator<<(std::endl);return 0;
}
混合捕获
形式 | 原理 | 成员变量类型 |
---|---|---|
[=, &var] | 除var外全值捕获,var引用捕获 | 值类型成员 + var引用成员 |
[&, var] | 除var外全引用捕获,var值捕获 | 引用类型成员 + var值成员 |
[var1, &var2] | 显式指定值/引用捕获 | var1值成员 + var2引用成员 |
示例混合捕获展开代码:
// [=,&s] 捕获
class __lambda_9_11 {std::string& s; // 引用捕获int a; // 值捕获
};
[=,&variable]拷贝及部分引用捕获
源:
#include <string>
#include <iostream>int main()
{int a = 10;std::string s = "hello";// 默认是常函数void operator()() constauto f = [=,&s]() {s += std::to_string(a);std::cout << a << std::endl;// 报错:表达式必须是可修改的左值// a += 100;};std::cout << s << std::endl;f();std::cout << s << std::endl; // hello10return 0;
}
cppinsights展开:
#include <string>
#include <iostream>int main()
{int a = 10;std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());class __lambda_9_11{public:inline /*constexpr */ void operator()() const{s.operator+=(std::to_string(a));std::cout.operator<<(a).operator<<(std::endl);}private:std::basic_string<char>& s;int a;public:__lambda_9_11(std::basic_string<char>& _s, int& _a): s{ _s }, a{ _a }{}};__lambda_9_11 f = __lambda_9_11{ s, a };std::operator<<(std::cout, s).operator<<(std::endl);f.operator()();std::operator<<(std::cout, s).operator<<(std::endl);return 0;
}
[&,variable]引用及部分拷贝捕获
源:
#include <string>
#include <iostream>int main()
{int a = 10;std::string s = "hello";// 默认是常函数void operator()() constauto f = [&,a]() {s += std::to_string(a);std::cout << a << std::endl;// 报错:表达式必须是可修改的左值;“a” : 无法在非可变 lambda 中修改通过复制捕获// a += 100;};std::cout << s << std::endl; // hellof();std::cout << s << std::endl; // hello10return 0;
}
cppinsights展开:
#include <string>
#include <iostream>int main()
{int a = 10;std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());class __lambda_9_11{public:inline /*constexpr */ void operator()() const{s.operator+=(std::to_string(a));std::cout.operator<<(a).operator<<(std::endl);}private:int a;std::basic_string<char>& s;public:__lambda_9_11(int& _a, std::basic_string<char>& _s): a{ _a }, s{ _s }{}};__lambda_9_11 f = __lambda_9_11{ a, s };std::operator<<(std::cout, s).operator<<(std::endl);f.operator()();std::operator<<(std::cout, s).operator<<(std::endl);return 0;
}
显式捕获
-
值捕获
[var]
- 生成const成员变量
- 需mutable才能修改副本,修改不影响外部变量
-
引用捕获
[&var]
- 生成引用类型成员
- 修改直接影响外部变量
[variable]拷贝捕获部分变量
源:
#include <string>
#include <iostream>int main()
{int a = 10;std::string s = "hello";// 默认是常函数void operator()() constauto f = [a]() {// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中// std::cout << s << std::endl;std::cout << a << std::endl;// 报错:表达式必须是可修改的左值;“a” : 无法在非可变 lambda 中修改通过复制捕获// a += 100;};std::cout << s << std::endl; // hellof();return 0;
}
cppinsights展开:
#include <string>
#include <iostream>int main()
{int a = 10;std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());class __lambda_9_11{public:inline /*constexpr */ void operator()() const{std::cout.operator<<(a).operator<<(std::endl);}private:int a;public:__lambda_9_11(int& _a): a{ _a }{}};__lambda_9_11 f = __lambda_9_11{ a };std::operator<<(std::cout, s).operator<<(std::endl);f.operator()();return 0;
}
[&variable]引用捕获部分变量
源:
#include <string>
#include <iostream>int main()
{int a = 10;std::string s = "hello";// 默认是常函数void operator()() constauto f = [&a]() {// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中// std::cout << s << std::endl;a += 100;};std::cout << a << std::endl; // 10f();std::cout << a << std::endl; // 110return 0;
}
cppinsights展开:
#include <string>
#include <iostream>int main()
{int a = 10;std::basic_string<char> s = std::basic_string<char>("hello", std::allocator<char>());class __lambda_9_11{public:inline /*constexpr */ void operator()() const{a = a + 100;}private:int& a;public:__lambda_9_11(int& _a): a{ _a }{}};__lambda_9_11 f = __lambda_9_11{ a };std::cout.operator<<(a).operator<<(std::endl);f.operator()();std::cout.operator<<(a).operator<<(std::endl);return 0;
}
[this]拷贝捕获this
-
原理:捕获当前对象的this指针
-
实现方式:
class __lambda_13_12 {MyClass* __this; // 存储this指针 public:void operator()() const {__this->a += 100; // 访问成员变量} };
-
注意:当使用
[=]
或[&]
时会隐式捕获this
源:
#include <string>
#include <iostream>class MyClass
{
public:MyClass() = default;~MyClass() = default;void lambdaCapture(){// 默认是常函数void operator()() constauto f = [this]() {// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中// std::cout << s << std::endl;a += 100;};std::cout << a << std::endl; // 10f();std::cout << a << std::endl; // 110}
private:int a = 10;std::string s = "hello";
};int main()
{MyClass().lambdaCapture();return 0;
}
cppinsights展开:
#include <string>
#include <iostream>class MyClass
{public:inline constexpr MyClass() noexcept(false) = default;inline ~MyClass() noexcept = default;inline void lambdaCapture(){class __lambda_13_12{public:inline /*constexpr */ void operator()() const{__this->a = __this->a + 100;}private:MyClass* __this;public:__lambda_13_12(MyClass* _this): __this{ _this }{}};__lambda_13_12 f = __lambda_13_12{ this };std::cout.operator<<(this->a).operator<<(std::endl);f.operator()();std::cout.operator<<(this->a).operator<<(std::endl);}private:int a;std::basic_string<char> s;
public:
};int main()
{MyClass().lambdaCapture();return 0;
}