目录
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()() const
auto 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); // 无法改变外部变量s
a += 100; // 可以改变外部变量a
std::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 = 10
return 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()() const
auto 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; // hello10
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
{
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()() const
auto f = [&,a]() {
s += std::to_string(a);
std::cout << a << std::endl;
// 报错:表达式必须是可修改的左值;“a” : 无法在非可变 lambda 中修改通过复制捕获
// a += 100;
};
std::cout << s << std::endl; // hello
f();
std::cout << s << std::endl; // hello10
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
{
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()() const
auto f = [a]() {
// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
// std::cout << s << std::endl;
std::cout << a << std::endl;
// 报错:表达式必须是可修改的左值;“a” : 无法在非可变 lambda 中修改通过复制捕获
// a += 100;
};
std::cout << s << std::endl; // hello
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_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()() const
auto f = [&a]() {
// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
// std::cout << s << std::endl;
a += 100;
};
std::cout << a << std::endl; // 10
f();
std::cout << a << std::endl; // 110
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
{
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()() const
auto f = [this]() {
// 报错:封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中
// std::cout << s << std::endl;
a += 100;
};
std::cout << a << std::endl; // 10
f();
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;
}