0%

C++ lambda表达式

C++11引入的lambda表达式提供了按引用和按值两种捕获模式。在使用按引用捕获时,需警惕空悬引用问题,即闭包内的引用可能指向已销毁的局部变量或形参。

按值捕获虽然避免了空悬引用,但在捕获类成员时,仅捕获this指针的副本,而非成员变量的副本。lambda表达式不能捕获static变量,只能按引用使用。

相比于std::bind,lambda表达式更灵活,特别是在处理函数重载和延迟计算时,lambda表达式更具优势,建议优先使用。

C++11 中有两种捕获模式: 按引用和按值。按引用的默认捕获模式可能导致空悬引用,按值的默认捕获模式也无法对空悬引用免疫,而且会让你认为你的闭包是独立的(事实上它们可能不是独立的)。

按引用捕获会导致闭包包含指涉到局部变量的引用,或者指涉到定义lambda式的作用域内的形参的引用。一旦由lambda式所创建的闭包越过了该局部变量或形参的生命周期,那么闭包内的引用就会空悬。

1
2
3
4
5
6
7
8
9
10
11
std::vector<std::function<int(int)>> vec;

int f() {
int divisor = 0;
vec.push_back([&](int val){ return val / divisor;});
}

int f2() {
auto lamdba = vec.at(0);
lambda(1); ///< 此处可能会出现空悬引用。
}

你可能会觉得如果把上面的按引用捕获换为按值捕获这样的空悬引用就不会出现。

1
2
3
4
5
6
7
8
9
10
11
  std::vector<std::function<int(int)>> vec;
class object {
public:
int f() {
int divisor = 0;
vec.push_back([=](int val){ return val / divisor;}); ///< 这里是引用捕获
}

private:
int divisor = 0;
}

引用捕获只能针对于在创建lambda式的作用域内可见的非静态局部变量(包括形参);

上面代码等价于这样

1
2
3
4
5
6
7
8
9
10
11
12
13
class object {
public:
int f() {
int divisor = 0;
vec.push_back([=](int val){ return val / divisor;});
/// 等价于下面这句
/// vec.push_back([this](int val){ return val / this->divisor;});
/// 默认捕获列表捕获的是this的副本,而不是divisor的副本
}

private:
int divisor = 0;
}

lambda表达式同样不能捕获static变量, 在lambda表达式中使用static变量只能是按引用。

lambda 表达式与 std::bind 优先选用lambda

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

在创建setSoundB这函数对象的时候,通过std::bind去创建的时候steady_clock::now()已经进行了计算,而不是在调用时刻进行计算,
同样在具有函数重载的情况时,std::bind接收的只是函数名称无法参与函数重载的判断,会导致编译不过。