lambda 表达式:
std::find_if(container.begin(), container.end(),
[](int val){ return 0 < val && val < 10; }); //译者注:本行高亮
- 闭包:是 lambda 创建的运行期对象。依赖捕获模式,闭包持有被捕获数据的副本或者引用。在上面的
std::find_if
调用中,闭包是作为第三个实参在运行时传递给std::find_if
对象。 - 闭包类:是从中实例化闭包的类。每个 lambda 都会使编译器生成唯一的闭包类。lambda 中的语句成为其闭包类的成员函数中的可执行指令。
闭包通常可拷贝,所以可能有多个闭包对应于一个 lambda。
条款31 避免使用默认捕获模式
按引用捕获会导致闭包中包含对某个局部变量或者形参的引用,变量或形参只在定义 lambda 的作用域中可见。
如果 lambda 创建的闭包生命周期长过局部变量或形参的生命周期,那么闭包中的引用将变成悬空引用。
using FilterContainer = //“using”参见条款9,
std::vector<std::function<bool(int)>>; //std::function参见条款2
FilterContainer filters; //过滤函数
void addDivisorFilter()
{
auto calc1 = computeSomeValue1();
auto calc2 = computeSomeValue2();
auto divisor = computeDivisor(calc1, calc2);
filters.emplace_back( //危险!对divisor的引用
[&](int value) { return value % divisor == 0; } //将会悬空!
);
}
lambda 对局部变量divisor
进行引用,但是该变量的生命周期会在函数返回时结束。
使用拷贝捕获,但如果遇到指针。并不能避免 lambda 外部对这个指针的delete
,导致副本指针变成悬空指针。
捕获只能应用于lambda
被创建的时候所在作用域里的non-static
局部变量(包括形参)。注意是变量。
class Widget {
public:
… //构造函数等
void addFilter() const; //向filters添加条目
private:
int divisor; //在Widget的过滤器使用
};
void Widget::addFilter() const
{
filters.emplace_back(
[=](int value) { return value % divisor == 0; }
);
}
这里没有捕获到divisor
,因为divisor
是一个成员变量而不是作用域内的局部变量,捕获到的其实一个 this 指针,在任何 Widget 成员函数中,对成员变量的访问都会隐式地使用this->val
。
这样就会报错:
在C++14中一个更好的捕获成员变量的方式是使用通用的 lambda 捕获:
void Widget::addFilter() const
{
filters.emplace_back( //C++14:
[divisor = divisor](int value) //拷贝divisor到闭包
{ return value % divisor == 0; } //使用这个副本
);
}
这样编写代码一点问题都没有,多亏C++14。
使用默认的按值捕获,它们预示了相关的闭包是独立的并且不受外部数据变化的影响。
静态存储生命周期对象(static)也可以在 lambda 中使用,但是不能被捕获,而是直接使用。
条款32 使用初始化捕获来移动对象到闭包中
C++11不支持移动对象到闭包中,但是C++14支持。(初始化捕获)
使用初始化捕获可以指定:
- 从 lambda 生成的闭包类中的数据成员名称
- 初始化该成员的表达式
class Widget { //一些有用的类型
public:
…
bool isValidated() const;
bool isProcessed() const;
bool isArchived() const;
private:
…
};
auto pw = std::make_unique<Widget>(); //创建Widget;使用std::make_unique
//的有关信息参见条款21
… //设置*pw
auto func = [pw = std::move(pw)] //使用std::move(pw)初始化闭包数据成员
{ return pw->isValidated()
&& pw->isArchived(); };
捕获部分的表达式pw = std::move(pw)
,等号左侧的作用域是闭包类,右侧作用域和 lambda 定义所在的定义域相同。
如果在C++11中没有初始化捕获,可以使用std::bind
,而且将捕获转变为参数:
auto func = std::bind(
[](const std::unique_ptr<Widget>& pw)
{ return pw->isValidated()
&& pw->isArchived(); },
std::make_unique<Widget>()
);
条款33 对auto&&
形参使用decltype
以std::forward
它们
在泛型 lambda 中如果碰到类似:
auto f = [](auto&& x)
{ return func(normalize(std::forward<???>(x))); };
forward
后面的尖括号中应该填什么? 因为 lambda 没有模板,所以没有不能填诸如T
的类型。
传递给通用引用的是左值,形参会变为左值引用;右值,会变为右值引用。所以可以通过使用decltype
来检查传入实参是左值还是右值。
因此,lambda 的完美转发可以写成:
auto f =
[](auto&& param)
{ return func(normalize(std::forward<decltype(param)>(param))); };
条款34 考虑lambda而非std::bind
略……