捕获的潜在问题¶
值捕获只保存捕获时的对象状态:
int x = 1;
auto f = [x] {return x; };
auto g = [x]()mutable {return ++x; };
x = 42; //@ 不会改变lambda中已经捕获的x
cout << f() << " " << g() << "\n"; //@ 1 2
引用捕获会保持与被捕获对象状态一致:
int x = 1;
auto f = [&x] {return x; };
x = 42;
cout << f() <<"\n"; //@ 42
引用捕获时,在捕获的局部变量析构后调用 lambda,将出现空悬引用:
std::function<void()> f()
{
int x = 0;
return [&]() { std::cout << x << "\n"; };
}
f()(); //@ x已被析构,值未定义
值捕获的问题比较隐蔽:
struct A {
auto f()
{
return [=] { std::cout << i << "\n"; };
};
int i = 1;
};
A a;
a.f()(); //@ 1
上述代码看似没有问题,但如果把 = 去掉,或者显式捕获,就会出现无法捕获的错误。原因在于数据成员位于 lambda 作用域外,不能被捕获:
struct A {
auto f()
{
return [i] { std::cout << i<<"\n"; }; //@ 错误:无法捕获i
};
int i = 1;
};
之前不出错的原因在于,= 捕获的是 this 指针,实际效果相当于引用捕获:
struct A {
auto f()
{
return [this] { std::cout << i << "\n"; }; //@ i被视为this->i
};
int i = 1;
};
A a;
auto x = a.f(); //@ 内部的i与a.i绑定
a.i = 2;
因此值捕获也可以出现空悬引用:
struct A {
auto f()
{
return [this] { std::cout << i << "\n"; };
};
int i = 1;
};
auto g()
{
auto p = std::make_unique<A>();
return p->f();
} //@ p被析构
this 指针会被析构,可以值捕获 *this 对象(C++ 17),这才是真正的值捕获:
struct A {
auto f()
{
return[*this]{ std::cout << i << "\n"; };
};
int i = 1;
};
//@ 现在lambda捕获的是对象的一个拷贝,不会受原对象的析构的影响
auto g()
{
auto p = std::make_unique<A>();
return p->f();
}
A a;
auto x = a.f(); // 只保存此刻的a.i
a.i = 2;
x(); //@ 1
g()(); //@ 1
更细致的解决方法是,捕获数据成员的一个拷贝:
struct A {
auto f()
{
int j = i;
return [j] { std::cout << j << "\n"; };
};
int i = 1;
};
auto g()
{
auto p = std::make_unique<A>();
return p->f();
}
g()(); //@ 1
C++14 中提供了广义 lambda 捕获,也叫初始化捕获,可以直接在捕获列表中拷贝:
struct A
{
auto f()
{
return [i = i] { std::cout << i << "\n"; };
};
int i = 1;
};
auto g = [p = std::make_unique<A>()]{ return p->f(); };
g()(); //@ 1
值捕获似乎表明闭包是独立的,与闭包外的数据无关,但并非如此,比如 lambda 可以直接使用 static 变量(但无法捕获):
static int i = 1;
auto f = [] { std::cout << i << "\n"; }; // OK:可以直接使用i
auto g = [i] {}; //@ 错误:无法捕获static变量