Effective Modern C++ 条款32:使用初始化捕获将对象移入闭包
Effective Modern C++ 条款32:使用初始化捕获将对象移入闭包
- 为什么需要移动捕获?
- C++14的解决方案:初始化捕获
- 基本用法示例
- 直接初始化示例
- C++11中的模拟实现
- 方法1:手动实现函数对象
- 方法2:使用std::bind模拟
- std::unique_ptr的模拟示例
- 关键要点总结
- 结论
在C++开发中,lambda表达式已经成为现代C++编程不可或缺的一部分。然而,在C++11中,lambda捕获机制存在一个明显的限制:无法将对象移动(move)到闭包中。这个问题在C++14中通过"初始化捕获"(init capture)得到了解决。本文将深入探讨这一特性及其应用场景。
为什么需要移动捕获?
在C++11中,lambda表达式只能通过两种方式捕获外部变量:
- 按值捕获(复制)
- 按引用捕获(依赖外部生命周期)
这两种方式在某些场景下都不理想。考虑以下两种情况:
- 当你有一个只能被移动的对象(如
std::unique_ptr或std::future)需要进入闭包时 - 当你要复制的对象复制开销很高,但移动成本很低(如标准库容器),而你希望移动而非复制该对象到闭包中
在C++11中,这两种需求都无法直接满足,这被认为是C++11的一个缺点。
C++14的解决方案:初始化捕获
C++14引入了初始化捕获(也称为通用lambda捕获)来解决这个问题。初始化捕获允许你:
- 指定从lambda生成的闭包类中的数据成员名称
- 指定初始化该成员的表达式
基本用法示例
auto pw = std::make_unique<Widget>(); // 创建Widgetauto func = [pw = std::move(pw)] { // 使用std::move(pw)初始化闭包数据成员return pw->isValidated() && pw->isArchived();
};
在这个例子中:
=左侧的pw表示闭包类中的数据成员=右侧的pw表示lambda上方声明的对象pw = std::move(pw)表示"在闭包中创建数据成员pw,并使用将std::move应用于局部变量pw的结果来初始化该数据成员"
直接初始化示例
如果不需要在捕获前修改对象,我们可以直接初始化闭包成员:
auto func = [pw = std::make_unique<Widget>()] {return pw->isValidated() && pw->isArchived();
};
C++11中的模拟实现
如果你的项目仍在使用C++11,有几种方法可以模拟移动捕获的效果。
方法1:手动实现函数对象
class IsValAndArch {
public:using DataType = std::unique_ptr<Widget>;explicit IsValAndArch(DataType&& ptr) : pw(std::move(ptr)) {}bool operator()() const {return pw->isValidated() && pw->isArchived();}private:DataType pw;
};auto func = IsValAndArch(std::make_unique<Widget>());
这种方法虽然代码量多,但能实现相同的功能。
方法2:使用std::bind模拟
更简洁的方法是使用std::bind:
std::vector<double> data;auto func = std::bind([](const std::vector<double>& data) { /* 使用data */ },std::move(data)
);
这种方法的原理是:
- 将要捕获的对象移动到由
std::bind产生的函数对象中 - 将被捕获对象的引用传递给lambda
对于mutable lambda,需要稍作修改:
auto func = std::bind([](std::vector<double>& data) mutable { /* 使用data */ },std::move(data)
);
std::unique_ptr的模拟示例
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来模拟初始化捕获
- 原理理解:
- 无法移动构造一个对象到C++11闭包
- 但可以将对象移动构造进C++11的bind对象
- 通过传引用将移动构造的对象传递给lambda
结论
初始化捕获是C++14中一个强大而灵活的特性,它解决了C++11 lambda表达式在移动语义方面的限制。对于仍在使用C++11的开发者,理解如何通过std::bind或手动实现函数对象来模拟这一功能同样重要。随着现代C++的发展,合理运用这些特性可以写出更高效、更清晰的代码。
