Chapter 6 Lambda Expressions
首先要搞清楚几个概念:
lambda 表达式:就是一个表达式,是代码本身:
c++1
[](int val) { return 0 < val && val < 10; }
closure:由 lambda 表达式创建出来的运行时对象,closure 包含 capture list 中的数据。
closure class:closure 对象所属的类。每有一个 lambda 表达式,编译器就会生成一个 closure class。lambda 表达式中的语句就出现在 closure class 的成员函数中。
注意,lambda 表达式和 closure class 存在于编译时刻,closure 存在于运行时刻。
Item 31 Avoid default capture modes
使用默认的 by-reference capture 容易导致悬空引用的问题,除非能保证被捕获的变量生命周期不短于 lambda。指定 capture 变量至少能让问题变得更容易解决一些。
使用 by-value capture 也会有悬空指针的问题(使用默认的 capture mode 将使这个问题更加隐蔽),尤其是 capture this 指针的时候,一个解决方法是将变量拷贝一份,然后 capture by value:
c++1
2
3
4
5
6
7void Widget::addFilter() const {
auto divisorCopy = divisor; // divisor is a data member
filters.emplace_back(
[divisorCopy](int value) // capture the copy
{ return value % divisorCopy == 0; } // use the copy
);
}C++14 中还可以使用 init capture 使代码更简洁:
c++1
2
3
4
5
6void Widget::addFilter() const {
filters.emplace_back(
[divisor = divisor](int value) // copy divisor to closure
{ return value % divisor == 0; } // use the copy
);
}
Item 32 Use init capture to move objects into closures
C++14 引入的 init capture 使 lambda 表达式更加强大灵活。使用 init capture,在
=
左边指定 closure class 中成员变量的变量名,在=
右边指定由于初始化该成员变量的表达式。init capture 可以用来移动构造 capture 变量,在 C++11 中也有方法来模拟这一点:用对象来移动构造一个 bind object(
std::bind
生成的对象),然后将该对象引用传参给 lambda:c++1
2
3
4auto func = std::bind(
[](const std::vector<double>& data) {},
std::move(data)
);std::bind
的第二个参数是右值,所以被用来移动构造 bind object(func
),当func
被调用时,其中的成员变量被传递给std::bind
的第一个参数,由于是引用传递,所以总的开销也就只有一次 move,和使用 init capture 一样。
Item 33 Use decltype on auto&& parameters to std::forward them
C++14 还引入了 generic lambda,即 labmda 参数类型可以是
auto
,这个机制可以用来实现 lambda 的完美转发:c++1
2
3auto fwd = [](auto&& param) {
f(std::forward<decltype(param)>(param));
};和函数模板实现的完美转发相比,区别主要在于
forward
的类型参数:c++1
2
3
4template<typename T>
void fwd(T&& param) {
f(std::forward<T>(param));
}以
int
为例,当入参为左值时,前者param
是int&
,后者T
也是int&
,forward
表现相同。但是当入参为右值时,情况有所不同:前者param
是int&&
,后者T
是int
,但是仔细看一下forward
的实现就会发现,由于 reference collapsing,两者最终的表现仍然是相同的:c++1
2
3
4template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
Item 34 Prefer lambdas to std::bind
- 和
std::bind
相比,lambda 表达式在诸多方面要更加好用,例如延迟求值,很好处理重载函数,内联,可读性更高等等。 - C++14 中没有理由再使用
std::bind
而不用 lambda 表达式了,因为它解决了 C++11 中 lambda 表达式两个小问题:- move capture,C++14 引入 init capture 可以移动构造 capture 变量。
- polymorphic function objects,本质是
bind
的第一个参数是 callable,这个 callable 可以是个函数模板,而 C++14 引入 lambda 表达式的auto
参数,从而也支持了这一点。