如果你有一个对象,其复制操作开销昂贵,而移动操作成本低廉,而你又需要把该对象放入闭包,那么你肯定更愿意移动该对象,而非复制它。
C++14为我们提供了一个全新的捕获方式---初始化捕获,也叫做广义lambda捕获,使用初始化捕获,则你会得到机会指定:
-
由lambda生成的闭包类中的成员变量名字。
-
一个表达式,用以初始化该成员变量。
例如使用初始化捕获将std::unqiue_ptr
移动到闭包内:
class Widget { //一些有用的型别
public:
...
bool isValidted() const;
bool isProcessed() const;
bool isArchived() const;
private:
...
};
auto pw = std::make_unique<Widget>(); //创建Widget
//关于std::make_unique,参见Item 21
... //配置*pw
auto func = [pw = std::move(pw)] //采用std::move(pw)
{ return pw->isValidated() && //初始化闭包类的数据成员
pw->isArchived();};
[] 中的那段代码就是初始化捕获,位于“=”左边的,是你所指定的闭包类成员变量的名字,而位于其右侧是初始化表达式,且它们处于不同的作用域,左侧作用域就是闭包类的作用域,右侧的作用域与lambda式加以定义之处的作用域相同。
pw=std::move(pw)
表达了"在闭包中创建一个成员变量pw,然后使用针对局部变量pw实施std::move的结果来初始化该成员变量"。
C++11显然并不支持这种初始化捕获,那该如何在C++11中实现移动捕获呢?
在C++11中按移动捕获可以采用以下方法模拟,只需要:
- 把需要捕获的对象移动到
std::bind
产生的函数对象中。 - 给到lambda式一个指涉到欲“捕获”的对象的引用。
例如,如果你想创建一个局部的std::vector对象,向其放入一组值,然后将其移动入闭包。在C++14使用初始化捕获,非常简单:
std::vector<double> data; //欲移入闭包的对象
... //灌入数据
auto func = [data = std::move(data)] //C++14的初始化捕获
{/* 对数据加以运用 */}
但是在C++11中,你必须借助std::bind生成函数对象:
std::vector<double> data; //同前
... //同前
auto func = std::bind(
[](const std::vector<double>& data) //C++11中模拟移动捕获的部分
{/* 对数据加以运用 */},
std::move(data)
);
和lambda表达式类似地,std::bind
也生成函数对象,第一个实参是个可调用对象,第二个实参表示传给该对象的值。
我们知道,std::bind
对于每个左值实参,在绑定对象内的对应的对象内对其实施的是复制构造,而对每个右值实参,实施的则是移动构造。所以当一个绑定对象被“调用”,也就是当func(绑定对象)被调用时,func内经由移动构造出所得到的data的副本就会作为实参传递给那个原先传递给std::bind的lambda式。
至此,我们应该知道以下知识点:
- 移动构造一个对象入C++闭包是不可能实现的,但移动构造一个对象入绑定对象则是可以实现的
- 欲在C++11中模拟移动捕获,需要包含以下步骤:
- 先移动构造一个对象入绑定对象
- 然后按照引用把移动构造所得的对象传递给lambda表达式
- 因为绑定对象的生命周期和闭包相同,所以针对绑定对象中的对象和闭包内的对象可以采用相同手法处置