c++ Lambda表达式使用过程中可能遇到的坑

6,317 阅读2分钟

Lambda使用时可能遇到的坑

Lambda表达式给c++编程带来了很多的便利,但是在使用Lambda时需要非常小心其中可能遇到的问题,熟悉Lambda表达式才能顺利的避开各种坑

引用捕获可能带来悬挂引用

这个问题常见于使用Lambda表达式使用引用捕获某个局部变量,而调用Lambda表达式时,局部变量已经被清理导致,捕获的引用指向被清理的内存空间产生悬挂引用,例如:

#include "string"
#include "iostream"
#include "functional"

using Func = std::function<void()>;

Func GetFunc() {
  int a = 1;
  return [&](){ std::cout << a << std::endl; };
}

int main(int, char*[]) {
  std::function<void()> func = GetFunc();
  func();
  return 0;
}

在上面这段代码中,在Lambda表达式func中捕获了局部变量local的引用,局部代码块执行结束后,local的生命周期结束被清理了,在外面再调用func时就使用到了一个指向已经被清理局部变量的引用。

捕获this使用时可能this已经被销毁了

这个问题与上面类似,捕获了this,但是可能在使用Lambda时this指向的对象已经被销毁,从而产生悬挂引用。例如下面的代码:

#include "string"
#include "iostream"
#include "functional"

using Func = std::function<void()>;

class Test {
  public:
    Test(int num): num_(num) {};
    Func GetFunc() {
       return [=]() {
          std::cout << num_ << std::endl;
       };
    }
  private:
    int num_;
};

Func GetFunc() {
  Test test(1);
  return test.GetFunc();
}

int main(int, char*[]) {
  GetFunc()();
  return 0;
}

在上面的例子中,因为局部的Test对象local已经被销毁了,但是Lambda表达式func仍然持有该对象的引用,产生了悬挂引用。

在捕获函数自身时会出现错误

这个错误会出现在Lambda表达式中递归的调用自己,一般出现在递归函数,或者递归的回调函数中。因为引用了自身,但是引用的lambda表达式可能在创建一段时间以后被销毁掉,导致捕获的引用悬挂。例如

#include "string"
#include "iostream"
#include "functional"

volatile bool no_optimize = true; // 防止编译器优化掉而不能发现错误

using Func = std::function<int(int)>;

Func GetFactorialFunc() {
  Func fac = [&](int n) -> int {
    return n == 0 ? 1 : n * fac(n-1);
  };
  if (no_optimize)
    return fac;
  return [] (int n) { return n; };
}

int main(int, char*[]) {
  std::cout << GetFactorialFunc()(10) << std::endl;
  return 0;
}

在这个例子中因为回归调用引用的Lambda表达式是局部变量在函数结束时就已经被回收了,递归调用时因为使用了指向悬挂引用导致出错。