用初始化捕获将对象移入闭包¶
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 对象中的对象和闭包中的对象可以用同样的手法处理。