31:避免默认捕获模式
- 闭包:lambda所创建的运行期对象
- 默认捕获可能导致引用悬挂
- 默认传引用可能导致引用悬挂
- 显式传引用也可能导致引用悬挂,但是可以更容易发现此处可能有引用悬挂
- 默认传值捕获也可能导致引用悬挂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
std::vector<std::function<bool(int)>> filters; class Widget{ public: // void addFilter() const{ // filters.emplace_back( // [=](int value) {return value % divisor == 0;} // ); // 看似是传值捕获,不会有引用悬挂;但是lambda只能捕获作用域中的非静态局部变量,此处的divisor其实是this->divisor,容易产生引用悬挂 // } // 解决方法:使用一个局部变量复制成员变量,然后使用显式的值捕获 void addFilter() const{ int divisorCopy = divisor; filters.emplace_back( [divisorCopy] (int value) {return value % divisorCopy == 0;} ); } private: int divisor; };
- 默认传引用可能导致引用悬挂
- lambda只能捕获作用域中的非静态局部变量,无法捕获静态或全局变量
- 捕获表示将值拷贝到闭包类中,而lambda中使用静态或全局变量,相当于是对外部的引用,因此此时lambda不是独立的
- 参考
32:使用初始化捕获将对象移入闭包
- C++14使用初始化捕获模式(也称广义lambda捕获)来实现移动捕获
|
|
- C++11使用
std::bind
间接实现移动捕获
|
|
33:泛型lambda的完美转发版本
对
auto&&
类型的形参使用decltype
,以std::forward
之
- 泛型lambda(C++14):可以使用auto声明形参(即闭包类中的
operator()
可以使用模板实现)1 2 3 4 5 6 7
auto f = [] (auto x) {return func(x);} // 闭包类中的operator()的大致实现:auto形参实际上是模板类型推导 class SomeCompilerGeneratedClassName{ public: template <typename T> auto operator() (T x) const {return func(x);} }
- 泛型lambda的完美转发版本:
1 2 3 4 5 6 7 8 9
auto f = [] (auto&& param) {return func( std::forward<decltype(param)>(param) );} // 闭包类中的operator()的大致实现 class SomeCompilerGeneratedClassName{ public: template <typename T> auto operator() (T&& param) const { return func( std::forward<decltype(param)>(param) ); } }; auto fs = [] (auto&&... params) {return func( std::forward<decltype(params)>(params)... );} // 变长参数版本
- 参考
34:优先选用lambda表达式,而非std::bind
对于C++11,除了个别边缘case,lambda比
std::bind
更有优势;C++14,lambda完全可以替代std::bind
- lambda可读性更强,更容易理解
- 使用
std::bind
需要保持参数位置,同时需要了解其实现机制std::bind
需要保持参数位置,因此使用时需要查看原来函数的声明,才能知道占位符对应的参数类型和参数含义;但是lambda形参列表很明确std::bind
默认将参数拷贝到绑定对象内部(可以使用std::ref
指定传引用),但是lambda可以明确指出值捕获还是引用捕获std::bind
绑定对象的函数调用使用了完美转发机制,但是lambda可以从形参列表中清晰看出传值还是传引用
1 2 3 4 5 6 7
Widget w; Logger logger; auto f = [w, &logger] (CompressLevel level) { return compress(w, level, logger); } // 捕获对象:w值捕获,logger引用捕获;形参:level传值 auto g = std::bind(compress, w, std::placeholders::_1, std::ref(logger)); // 需要对应参数顺序 // 绑定对象:w值绑定(复制),logger引用绑定;形参:level使用完美转发机制
std::bind
参数绑定和对象调用不是一个时间,因此可能出现逻辑错误(见参考)
- 使用
- lambda灵活性更强
- 如果
std::bind
绑定的函数存在重载版本,则编译器无法确定使用哪个版本的重载函数1 2 3 4 5 6 7
void func(int a); void func(int a, int b); auto f = [] (int b) { return func(0, b); } using funcType = void(int, int); auto bnd = std::bind(static_cast<funcType>(func), 0, std::placeholders::_1)
- 如果
- lambda可以内联
- 因为
std::bind
中绑定的是函数指针,需要在运行时才能确定;但是lambda中包含函数体,可以进行内联
- 因为
- 使用
std::bind
的两个场景:在C++11中- 使用
std::bind
间接实现移动捕获([[ch06-lambda表达式#32:使用初始化捕获将对象移入闭包|C++14支持移动捕获]]) - 使用
std::bind
绑定参数的完美转发机制,间接多态函数对象([[ch06-lambda表达式#33:泛型lambda的完美转发版本|C++14支持泛型lambda]])1 2 3 4 5 6 7 8
auto f = [callableObject] (const auto& param) { callableObject(param); }; class CallableObject{ public: template <typename T> void operator() (const T& param); }; auto g = std::bind(CallableObject(), std::placeholders::_1); // 将占位符参数完美转发到可调用对象的调用运算符中
- 使用
- 参考