Item32:使用初始化捕获来移动对象到闭包中
C++11 lambda 的捕获短板
我们知道 C++11 的 lambda 只有按值捕获和按引用捕获两种方式,但这两种方式都有局限,满足不了一些特殊场景:
- 对于只能移动、不能拷贝的对象,比如
std::unique_ptr、std::future,C++11 根本没法把它们放进闭包; - 对于拷贝成本很高的对象(比如大容器
std::vector),我们只想移动进闭包节约开销,C++11 也做不到,只能被迫拷贝。
这是 C++11 lambda 设计上的一个明显缺陷,而 C++14 直接补上了这个短板。
C++14 的完美解决方案:初始化捕获(init capture)
C++14 引入了初始化捕获,也叫通用 lambda 捕获,它的灵活性极强,移动捕获只是它的核心用法之一,甚至能直接在捕获里创建对象,完全覆盖了 C++11 捕获的功能,还能做更多事。
1. 初始化捕获的语法逻辑
1 | [闭包内的数据成员名 = 初始化表达式] |
- 等号左侧:是 lambda 闭包类里自定义的数据成员名称;
- 等号右侧:是初始化这个成员的表达式,可以是移动、函数调用、临时对象等。
两者作用域不同:左侧属于闭包类,右侧是 lambda 定义的外部作用域。
2. 两种经典使用场景
- 把已有对象移动进闭包
1 | auto pw = std::make_unique<Widget>(); |
- 直接在捕获里创建对象(连局部变量都不用)
1 | auto func = [pw = std::make_unique<Widget>()] { |
C++11 没有初始化捕获?两种模拟移动捕获的方法
如果编译器只支持 C++11,没法用初始化捕获,也能实现近似的移动捕获效果。
1. 手写仿函数类
lambda 本质就是编译器生成的闭包类,那我们可以手动写一个类,在构造函数里把对象移动到成员变量中,重载operator()实现 lambda 的逻辑,效果完全一致。
2. 用std::bind模拟(更实用、更常用)
这是 C++11 模拟移动捕获的主要思路:
- 先把要移动的对象移动构造到
std::bind生成的绑定对象里(bind 会对右值参数做移动构造); - 再让 lambda 以引用的方式,使用 bind 对象里存储的这个移动后的对象;
- bind 对象的生命周期和闭包绑定,完全不用担心悬空问题。
比如移动vector进闭包的写法:
1 | std::vector<double> data; |
总结
- C++14 环境下,直接用初始化捕获把对象移动到闭包中,简洁又安全;
- C++11 没有初始化捕获,就通过手写仿函数类或者std::bind的方式,模拟实现移动捕获的效果。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论
