条款32:使用初始化捕获来移动对象到闭包中
条款32:使用初始化捕获来移动对象到闭包中
核心问题
在 C++11 中,lambda 无法将对象移动进闭包,只能:
- 按值捕获(复制)
- 按引用捕获(依赖外部生命周期)
这在以下两种情况下非常不便:
- 捕获的对象是 只能移动 的(如
std::unique_ptr,std::future) - 捕获的对象 复制开销大(如
std::vector,std::string),但移动代价低
C++14 解决方案:初始化捕获(Init Capture)
初始化捕获又称“通用 lambda 捕获(Generalized Lambda Capture)”,引入于 C++14。
语法:
[变量名 = 初始化表达式]
示例:移动 unique_ptr 进闭包
auto pw = std::make_unique<Widget>();
auto func = [pw = std::move(pw)] {
return pw->isValidated() && pw->isArchived();
};
更简洁的写法:
auto func = [pw = std::make_unique<Widget>()] {
return pw->isValidated() && pw->isArchived();
};
=左边:闭包内成员变量名=右边:定义 lambda 时的作用域表达式
C++11 如何模拟初始化捕获?
方法一:手写类
// 类 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 和类的关系本质上是什么?
auto f = [x]() { return x + 1; };
编译器会把上面 lambda 转换成这样一个类:
class Lambda {
int x;
public:
Lambda(int x_) : x(x_) {}
int operator()() const { return x + 1; }
};
Lambda f(5);
- 每个 lambda 本质上就是一个带有 operator() 的类对象,捕获变量就像类的成员变量。
方法二:使用 std::bind + lambda 引用参数
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,可以传非常量引用:
auto func = std::bind(
[](std::vector<double>& data) mutable {
// 可以修改 data 副本
},
std::move(data)
);
示例:模拟 unique_ptr 捕获
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 限制场景 |

浙公网安备 33010602011771号