捕获的潜在问题

值捕获只保存捕获时的对象状态:

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变量