本文包含《Effective Modern C++》中第6章内容的个人笔记,包含lambda表达式在C++11/14标准中的使用问题。
第六章 lambda表达式
条款31: 避免默认捕获模式
“捕获只能针对于在创建lambda式的作用域内可见的非静态局部变量(包括行參)”在书写lambda式的捕获语句时需要牢记这句话。
按默认捕获形式[]、[=]、[&]书写代码的情况容易使人忽略局部变量生存期的事实,从而导致指针空悬,也容易误导人使人们认为lambda是自洽的,所以应当显式声明lambda捕获的变量。
条款32: 使用初始化捕获将对象移入闭包
C++11标准中将一个对象移动至lambda闭包内是无法实现的,所以C++14标准扩展了lambda规则,增加了广义lambda捕获(generalized lambda capture)。广义lambda捕获使移动对象至lambda闭包内成为可能:
1 | std::vector<double> data; |
捕获表达式等号左边的data为闭包成员对象,右边为捕获表达式。
对于只支持C++11的编译器环境,梅耶斯也提供了一个思路以实现移动对象入闭包,通过std::bind:
1 | std::vector<double> data; |
即通过bind创建绑定对象,将对象移入绑定对象内,该对象通过引用传递给内部的lambda闭包,而lambda闭包和绑定对象生命周期相同,所以只要绑定对象存在则内部的闭包可以自由使用移入的对象。
另一种方法则是通过定义仿函数类型去实现,将对象移入仿函数对象,然后通过operator()去调用,相对bind的方法更啰嗦一点。
条款33: 对auto&&型别的形參使用decltype,以std::forward之
c++14标准中允许使用范型lambda,即可对lambda的形參使用auto,随之而来的问题是对于万能引用时的场景问题,形如:
1 | auto f = [](auto&& x) |
由于范型lambda并不像普通函数模板存在typename T,所以需要借助decltype来协助:
1 | auto f = |
仔细思考这一手法,传入左值时param为左值引用,decltype(param)为左值引用,forward<T>当T为左值引用时发生引用折叠返回左值引用;传入右值时param为右值引用,decltype(param)为右值引用,forward<T>当T为右值引用时发生引用折叠返回右值引用。
条款34: 优先考虑lambda而非std::bind
lambda表达式相比std::bind拥有更好的可读性,且不易出现因语义歧义造成的问题,例如书上提到的例子:
1 | auto setSoundB = |
steady_clock::now() + 1h的实际调用发生在bind的调用时间点,而非实际需要的在调用setSoundB的时间点。
同时由于std::bind内部实现函数调用是通过函数指针,这将导致bind的实现不太可能被编译器内联,而lambda是正常函数调用,其更可能被内联,也就更可能拥有更好的效率。
lambda可以指定所捕获的参数传值方式和入参传值方式,而std::bind对于“捕获”的实参是按值传递,对于placeholders参数通过完美转发进行传递,这在C++11标准中由于不允许使用泛型lambda,bind此时就能派上用场:
1 | class PolyWidget { |
bind的另一个c++11中的用处是lambda的移动捕获问题,详细在条款32中已经说明。
然而以上两个问题在c++14标准lambda规范中都已经解决,所以综上优先考虑lambda而非std::bind。