用初始化捕获将对象移入闭包

move-only 类型对象不支持拷贝,只能采用引用捕获:

auto p = std::make_unique<int>(42);
auto f = [&p]() {std::cout << *p << "\n"; };
f();

初始化捕获则支持把 move-only 类型对象移动进 lambda 中:

auto p = std::make_unique<int>(42);
auto f = [p = std::move(p)]() {std::cout << *p << "\n"; };
f();
assert(p == nullptr);

还可以直接在捕获列表中初始化 move-only 类型对象:

auto f = [p = std::make_unique<int>(42)]() {std::cout << *p << "\n"; };

如果不使用 lambda,C++11 中可以封装一个类来模拟 lambda 的初始化捕获:

class A {
public:
	A(std::unique_ptr<int>&& q) : p(std::move(q)) {}
	void operator()() const { std::cout << *p << "\n"; }
private:
	std::unique_ptr<int> p;
};

auto f = A(std::make_unique<int>(42));
f();

如果要在 C++11 中使用 lambda 并模拟初始化捕获,需要借助 std::bind

auto f = std::bind(
	[](const std::unique_ptr<int>& p) { std::cout << *p<<"\n"; },
	std::make_unique<int>(42));

bind 对象( std::bind 返回的对象)中包含传递给 std::bind 的所有实参的拷贝,对于每个左值实参,bind 对象中的对应部分由拷贝构造,对于右值实参则是移动构造。上例中第二个实参是右值,采用的是移动构造,这就是把右值移动进 bind 对象的手法。

std::vector<int> v; //@ 要移动到闭包的对象
//@ C++14:初始化捕获
auto f = [v = std::move(v)]{};
//@ C++11:模拟初始化捕获
auto g = std::bind([](const std::vector<int>& v) {}, std::move(v));

默认情况下,lambda 生成的闭包类的 operator() 默认为 const,闭包中的所有成员变量也会为 const,因此上述模拟中使用的 lambda 形参都为 const

auto f = [](auto x, auto y) { return x < y; };

//@ 上述lambda相当于生成如下匿名类
struct X {
	template<typename T, typename U>
	auto operator() (T x, U y) const { return x < y; }
};

如果是可变 lambda,则闭包中的 operator() 就不会为 const,因此模拟可变 lambda 则模拟中使用的 lambda 形参就不必声明为 const

std::vector<int> v;
//@ C++14:初始化捕获
auto f = [v = std::move(v)]() mutable {};
//@ C++11:模拟可变lambda的初始化捕获
auto g = std::bind([](std::vector<int>& v) {}, std::move(v));

因为 bind 对象的生命期和闭包相同,所以对 bind 对象中的对象和闭包中的对象可以用同样的手法处理。