lambda 表达式:

  1. std::find_if(container.begin(), container.end(),
  2. [](int val){ return 0 < val && val < 10; }); //译者注:本行高亮
  • 闭包:是 lambda 创建的运行期对象。依赖捕获模式,闭包持有被捕获数据的副本或者引用。在上面的std::find_if调用中,闭包是作为第三个实参在运行时传递给std::find_if对象。
  • 闭包类:是从中实例化闭包的类。每个 lambda 都会使编译器生成唯一的闭包类。lambda 中的语句成为其闭包类的成员函数中的可执行指令。

闭包通常可拷贝,所以可能有多个闭包对应于一个 lambda。

条款31 避免使用默认捕获模式

按引用捕获会导致闭包中包含对某个局部变量或者形参的引用,变量或形参只在定义 lambda 的作用域中可见

如果 lambda 创建的闭包生命周期长过局部变量或形参的生命周期,那么闭包中的引用将变成悬空引用

  1. using FilterContainer = //“using”参见条款9,
  2. std::vector<std::function<bool(int)>>; //std::function参见条款2
  3. FilterContainer filters; //过滤函数
  4. void addDivisorFilter()
  5. {
  6. auto calc1 = computeSomeValue1();
  7. auto calc2 = computeSomeValue2();
  8. auto divisor = computeDivisor(calc1, calc2);
  9. filters.emplace_back( //危险!对divisor的引用
  10. [&](int value) { return value % divisor == 0; } //将会悬空!
  11. );
  12. }

lambda 对局部变量divisor进行引用,但是该变量的生命周期会在函数返回时结束。

使用拷贝捕获,但如果遇到指针。并不能避免 lambda 外部对这个指针的delete导致副本指针变成悬空指针


捕获只能应用于lambda被创建的时候所在作用域里的non-static局部变量(包括形参)。注意是变量

  1. class Widget {
  2. public:
  3. //构造函数等
  4. void addFilter() const; //向filters添加条目
  5. private:
  6. int divisor; //在Widget的过滤器使用
  7. };
  8. void Widget::addFilter() const
  9. {
  10. filters.emplace_back(
  11. [=](int value) { return value % divisor == 0; }
  12. );
  13. }

这里没有捕获到divisor,因为divisor是一个成员变量不是作用域内的局部变量,捕获到的其实一个 this 指针,在任何 Widget 成员函数中,对成员变量的访问都会隐式地使用this->val
image.png
这样就会报错:
image.png

在C++14中一个更好的捕获成员变量的方式是使用通用的 lambda 捕获

  1. void Widget::addFilter() const
  2. {
  3. filters.emplace_back( //C++14:
  4. [divisor = divisor](int value) //拷贝divisor到闭包
  5. { return value % divisor == 0; } //使用这个副本
  6. );
  7. }

image.png
这样编写代码一点问题都没有,多亏C++14。

使用默认的按值捕获,它们预示了相关的闭包是独立的并且不受外部数据变化的影响

静态存储生命周期对象(static)也可以在 lambda 中使用,但是不能被捕获,而是直接使用。

条款32 使用初始化捕获来移动对象到闭包中

C++11不支持移动对象到闭包中,但是C++14支持。(初始化捕获)

使用初始化捕获可以指定

  • 从 lambda 生成的闭包类中的数据成员名称
  • 初始化该成员的表达式
  1. class Widget { //一些有用的类型
  2. public:
  3. bool isValidated() const;
  4. bool isProcessed() const;
  5. bool isArchived() const;
  6. private:
  7. };
  8. auto pw = std::make_unique<Widget>(); //创建Widget;使用std::make_unique
  9. //的有关信息参见条款21
  10. //设置*pw
  11. auto func = [pw = std::move(pw)] //使用std::move(pw)初始化闭包数据成员
  12. { return pw->isValidated()
  13. && pw->isArchived(); };

捕获部分的表达式pw = std::move(pw)等号左侧的作用域是闭包类,右侧作用域和 lambda 定义所在的定义域相同


如果在C++11中没有初始化捕获,可以使用std::bind,而且将捕获转变为参数:

  1. auto func = std::bind(
  2. [](const std::unique_ptr<Widget>& pw)
  3. { return pw->isValidated()
  4. && pw->isArchived(); },
  5. std::make_unique<Widget>()
  6. );

条款33 对auto&&形参使用decltypestd::forward它们

泛型 lambda 中如果碰到类似:

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

forward后面的尖括号中应该填什么? 因为 lambda 没有模板,所以没有不能填诸如T的类型。

传递给通用引用的是左值,形参会变为左值引用;右值,会变为右值引用。所以可以通过使用decltype来检查传入实参是左值还是右值。

因此,lambda 的完美转发可以写成:

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

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

略……