Item 32: Use init capture to move objects into closures.
Effective Modern C++ Item 32 的学习和解读。
如果你想移动一个对象都 lambda 闭包,值捕获和引用捕获都不能实现该目的。C++ 14 提供了初始化捕获(init capture)模式支持移动捕获。C++11 并不支持,但是可以使用 std::bind 间接模拟。
C++14 使用初始化捕获模式实现移动捕获
C++14 提供了支持移动捕获的机制,但并没有类似值捕获 [=] 或者引用捕获 [&] 的模式直接添加一个移动捕获 [&&] 模式。而是采取了一种更加灵活的机制 ----- 初始化捕获模式。移动捕获是采用初始化捕获的机制实现,除了默认捕获模式,初始化捕获模式可以做原来 C++11 支持的所有捕获模式能做的事,甚至还更多,比如 item31 中介绍的类成员变量的捕获。初始化捕获模式语法如下:
- 指定闭包类的成员名称。
- 指定一个表达式来初始化这个成员。
看下面的例子:移动一个 std::unique_ptr 对象到 lambda 闭包。
class Widget { // some useful type
public:
…
bool isValidated() const;
bool isProcessed() const;
bool isArchived() const;
private:
…
};
auto pw = std::make_unique<Widget>(); // create Widget;
// see Item 21 for info on
// std::make_unique
… // configure *pw
auto func = [pw = std::move(pw)] // init data mbr
{ return pw->isValidated() // in closure w/
&& pw->isArchived(); }; // std::move(pw)
在捕获列表中,“=” 左边的 pw
是指定的 lambda 闭包的成员名称。“=” 右边的 std::move(pw)
是指定的用于初始化闭包成员 pw
的表达式。“=” 两边的作用域也不一样,左边的作用域在 lambda 闭包中,右边的作用域是 lambda 表达式被定义的作用域。
C++11 使用 std::bind 间接实现移动捕获
C++11 使用 std::bind 间接实现移动捕获:
- 将被捕获对象移动至 std::bind 产生的函数对象中。
- 给 lambda 表达式一个被捕获对象的引用。
看下面的例子:
std::vector<double> data;
…
auto func =
std::bind( // C++11 emulation
[](const std::vector<double>& data) // of init capture
{ /* uses of data */ },
std::move(data)
);
和 lambda 表达式类似,std::bind 产生一个函数对象,称为绑定函数对象。std::bind 的第一个参数是可调用对象,紧接着的参数是传递给这个对象的值。
对于 std::bind 传递的参数,如果是左值,则拷贝到绑定函数对象中;如果是右值,则移动到绑定函数对象中。但是对于绑定函数对象而言,它的参数是一个左值引用。在这个例子中,std::bind 传递的是一个右值 std::move(data)
,func
内部调用移动构造来初始化 data
。
默认的,lambda 闭包内的 operator() 成员方法是一个 const 的,不能对参数进行修改,所以这里显示申明成 const。如果你希望可以对参数进行修改,则可以使用 mutable关键字进行修饰。
auto func =
std::bind( // C++11 emulation
[](std::vector<double>& data) mutable // of init capture
{ /* uses of data */ }, // for mutable lambda
std::move(data)
);
再给一个前面 C++14 std::unique_ptr 移动到 lambda 闭包的例子。
C++14 的实现如下:
auto func = [pw = std::make_unique<Widget>()] // as before,
{ return pw->isValidated() // create pw
&& pw->isArchived(); }; // in closure
C++11 间接实现如下:
auto func = std::bind(
[](const std::unique_ptr<Widget>& pw)
{ return pw->isValidated()
&& pw->isArchived(); },
std::make_unique<Widget>()
);
最后,总结下本文:
- C++14 使用初始化捕获模式实现移动捕获。
- C++11 使用 std::bind 间接实现移动捕获。