0%

Effective Modern C++读书笔记(五)

本文包含《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
2
std::vector<double> data;
auto func = [data = std::move(data)] { … }

捕获表达式等号左边的data为闭包成员对象,右边为捕获表达式。
对于只支持C++11的编译器环境,梅耶斯也提供了一个思路以实现移动对象入闭包,通过std::bind:

1
2
3
4
5
6
7
std::vector<double> data;
auto func =
std::bind(
[](const std::vector<double>& data)
{ … },
std::move(data)
);

即通过bind创建绑定对象,将对象移入绑定对象内,该对象通过引用传递给内部的lambda闭包,而lambda闭包和绑定对象生命周期相同,所以只要绑定对象存在则内部的闭包可以自由使用移入的对象。
另一种方法则是通过定义仿函数类型去实现,将对象移入仿函数对象,然后通过operator()去调用,相对bind的方法更啰嗦一点。

条款33: 对auto&&型别的形參使用decltype,以std::forward之

c++14标准中允许使用范型lambda,即可对lambda的形參使用auto,随之而来的问题是对于万能引用时的场景问题,形如:

1
2
auto f = [](auto&& x)
{ return func(normalize(std::forward<???>(x))); };

由于范型lambda并不像普通函数模板存在typename T,所以需要借助decltype来协助:

1
2
3
4
5
6
auto f =
[](auto&& param)
{
return
func(normalize(std::forward<decltype(param)>(param)));
};

仔细思考这一手法,传入左值时param为左值引用,decltype(param)为左值引用,forward<T>当T为左值引用时发生引用折叠返回左值引用;传入右值时param为右值引用,decltype(param)为右值引用,forward<T>当T为右值引用时发生引用折叠返回右值引用。

条款34: 优先考虑lambda而非std::bind

lambda表达式相比std::bind拥有更好的可读性,且不易出现因语义歧义造成的问题,例如书上提到的例子:

1
2
3
4
5
auto setSoundB =
std::bind(setAlarm,
steady_clock::now() + 1h,
_1,
30s);

steady_clock::now() + 1h的实际调用发生在bind的调用时间点,而非实际需要的在调用setSoundB的时间点。
同时由于std::bind内部实现函数调用是通过函数指针,这将导致bind的实现不太可能被编译器内联,而lambda是正常函数调用,其更可能被内联,也就更可能拥有更好的效率。
lambda可以指定所捕获的参数传值方式和入参传值方式,而std::bind对于“捕获”的实参是按值传递,对于placeholders参数通过完美转发进行传递,这在C++11标准中由于不允许使用泛型lambda,bind此时就能派上用场:

1
2
3
4
5
6
7
8
9
class PolyWidget {
public:
template<typename T>
void operator()(const T& param);

};

PolyWidget pw;
auto boundPW = std::bind(pw, _1);

bind的另一个c++11中的用处是lambda的移动捕获问题,详细在条款32中已经说明。
然而以上两个问题在c++14标准lambda规范中都已经解决,所以综上优先考虑lambda而非std::bind。