条款32:使用初始化捕获来移动对象到闭包中
通过初始化捕获,可将可移动对象(如unique_ptr)安全高效地移动进lambda闭包,避免拷贝开销。
条款32:使用初始化捕获来移动对象到闭包中
条款32:使用初始化捕获来移动对象到闭包中
核心问题
在 C++11 中,lambda 无法将对象移动进闭包,只能:
- 按值捕获(复制)
- 按引用捕获(依赖外部生命周期)
这在以下两种情况下非常不便:
- 捕获的对象是 只能移动 的(如
std::unique_ptr
,std::future
) - 捕获的对象 复制开销大(如
std::vector
,std::string
),但移动代价低
C++14 解决方案:初始化捕获(Init Capture)
初始化捕获又称“通用 lambda 捕获(Generalized Lambda Capture)”,引入于 C++14。
语法:
1
[变量名 = 初始化表达式]
示例:移动 unique_ptr 进闭包
1
2
3
4
5
auto pw = std::make_unique<Widget>();
auto func = [pw = std::move(pw)] {
return pw->isValidated() && pw->isArchived();
};
更简洁的写法:
1
2
3
auto func = [pw = std::make_unique<Widget>()] {
return pw->isValidated() && pw->isArchived();
};
=
左边:闭包内成员变量名=
右边:定义 lambda 时的作用域表达式
C++11 如何模拟初始化捕获?
方法一:手写类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 类 IsValAndArch 实现一个可调用对象(函数对象)
// 用于判断某个 Widget 是否已验证并归档
class IsValAndArch {
public:
// 定义数据成员类型:一个只能移动的智能指针
using DataType = std::unique_ptr<Widget>;
// 构造函数,接受一个右值引用,并将其移动到成员变量中
explicit IsValAndArch(DataType&& ptr)
: pw(std::move(ptr)) {} // std::move 确保唯一所有权转移到类内
// 函数调用操作符,使该类行为类似函数(lambda 替代品)
// 本函数为 const,因此不能修改 pw,也保证了线程安全
bool operator()() const {
// 调用 Widget 中的接口函数,返回逻辑判断结果
return pw->isValidated() && pw->isArchived();
}
private:
DataType pw; // 保存 Widget 的独占所有权
};
// 使用 std::make_unique 创建 Widget 实例,并立即构造 IsValAndArch 对象
// 然后调用其 operator(),获取结果
auto func = IsValAndArch(std::make_unique<Widget>())();
// 等价于:
// auto obj = IsValAndArch(std::make_unique<Widget>());
// auto result = obj();
lambda 和类的关系本质上是什么?
1
auto f = [x]() { return x + 1; };
编译器会把上面 lambda 转换成这样一个类:
1
2
3
4
5
6
7
class Lambda {
int x;
public:
Lambda(int x_) : x(x_) {}
int operator()() const { return x + 1; }
};
Lambda f(5);
- 每个 lambda 本质上就是一个带有 operator() 的类对象,捕获变量就像类的成员变量。
方法二:使用 std::bind
+ lambda 引用参数
1
2
3
4
5
6
7
8
9
10
std::vector<double> data = ...; // 要移动进闭包的对象,可能很大,复制开销高
auto func = std::bind(
[](const std::vector<double>& data) {
// 这里的 data 是传入的参数(通过 const 引用),实际上是 bind 内部保存的副本
// 可以安全读取 data,而不用担心外部对象被修改或销毁
},
std::move(data) // 将 data 移动到 bind 对象中,避免复制,完成“伪移动捕获”
);
// func 是一个可调用对象,调用时会传递 bind 内部保存的 data 副本给 lambda
std::move(data)
:将 data 资源移动到 std::bind 生成的函数对象中,避免复制开销。- lambda 的参数
const std::vector<double>& data
:引用的是 bind 对象内部保存的移动副本,保证数据有效且只读。
若 lambda 为 mutable
,可以传非常量引用:
1
2
3
4
5
6
auto func = std::bind(
[](std::vector<double>& data) mutable {
// 可以修改 data 副本
},
std::move(data)
);
示例:模拟 unique_ptr 捕获
1
2
3
4
5
6
auto func = std::bind(
[](const std::unique_ptr<Widget>& pw) {
return pw->isValidated() && pw->isArchived();
},
std::make_unique<Widget>()
);
注意事项
项 | 描述 |
---|---|
C++11 限制 | 不能捕获表达式结果(不能写 [x = foo()] ) |
bind 对象 | 会持有捕获对象的副本,与 lambda 生命周期一致 |
可修改性 | 默认 lambda 为 const ,若需修改绑定值,需加 mutable |
移动成本 | bind 对象中的实参为 值存储,右值会移动进来,左值会复制 |
可读性 | bind 用法不如 lambda 简洁直观,建议仅用于 C++11 限制场景 |
本文由作者按照 CC BY 4.0 进行授权