C++11 lambda学习笔记

1 lambda表达式

1.1 lambda表达式的构成

  1. 一个可能为空的捕获列表指明定义环境中的哪些名字能被用在lambda表达式内,以及这些名字的访问方式是拷贝还是引用,捕获列表位于[]内
  2. 一个可选的参数列表,指明lambda表达式所需的参数。参数列表位于()内
  3. 一个可选的mutable修饰符,指明该lambda表达式可能会修改它自身的状态,即改变通过值捕获的变量的副本
  4. 一个可选的noexcept修饰符
  5. 一个可选的->形式的返回类型声明
  6. 一个表达式体,指明需要执行的代码,表达式体位于{}内

1.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//一种类形式的替代方式
template<typename C>
class modulc_printer {
private:
using elementType=typename C::value_type;
ostream &os;
int m;
public:
modulc_printer(ostream&s,int mm) : os(s), m(mm) {}
void operator()(elementType x) const {
if (x%m==0) os<<x<<'\n';
}
};

template<typename C>
void print_modulc(const C& v, ostream &os, int m) {
for_each(begin(v), end(v),modulc_printer<typename remove_reference<decltype(v)>::type>(os,m));
}

template<typename C>
void print_modulf(const C& v, ostream &os, int m) {
for(auto x : v)
if (x%m==0) os<<x<<'\n';
}

template<typename C>
void print_modull(const C& v, ostream &os, int m) {
// error: no viable conversion from '(lambda at /home/chenpeng/Documents/TestCpp/src/Test.cpp:104:29)' to 'int (*)(int)'
// 这里编译器会在线生成一个闭包对象类型,因为函数指针无法携带捕获的函数变量等信息,所以lambda不是一个单纯的函数对象
// int (*lambdaobj)(int) = [&os,m](int x){ if (x%m==0) os<<x<<'\n'; };

// 如果lambda没有捕获任何外层变量,则可以使用一个单纯的函数指针保存它
int (*testFunPtr)(int) = [](int a) -> int { return a; };

// error: variable 'lambdaobj' declared with 'auto' type cannot appear in its own initializer
// lambda的实现是自身类型的一部分,所以类型推断无法推断一个包含自身类型的类型,这种情况必须使用下面的function对象保存lambda再进行递归调用
// auto lambdaobj = [&os,m,&lambdaobj](int x){ if (x%m==0) os<<x<<'\n'; if(x != 100) lambdaobj(100); };

// std::function<int(int)> lambdaobj = [&os,m,&lambdaobj](int x)->int { if (x%m==0) os<<x<<'\n'; if(x != 100) lambdaobj(100); return 0; };

// 这里获取到lambda对象用于尝试递归调用
std::function<void(int)> lambdaobj = [&os,m,&lambdaobj](int x) { if (x%m==0) os<<x<<'\n'; if(x != 100) lambdaobj(100); };
for_each(begin(v), end(v), lambdaobj);
}

int main(int argc, char *argv[]) {
vector<int> testvec{1,2,3,4,5,6,7,8,9};
print_modull(testvec,cout,2);
print_modulf(testvec,cout,2);
print_modulc(testvec,cout,2);
}

1.3 捕获

在 lambda表达式需要访问定义它的函数的局部名字时,lambda使用了引入符([]方括号)来解决这样的问题,lambda正是从引入符开始的。引入符有以下几种

  1. [] : 空捕获列表,表示在lambda中无法使用外层上下文中的任何局部名字。这种情况数据只能从参数和非局部变量获得
  2. [&] : 通过引用隐式捕获,所有外层局部变量都可以通过引用方式访问
  3. [=] : 通过值隐式捕获,所有名字都指向外层局部变量的副本
  4. [捕获列表] : 显示捕获,捕获列表是通过值或者引用的方式捕获外层局部变量的名字列表,以&为前缀的变量名字通过引用捕获,其他变量通过值捕获,捕获列表中可以出现this,或者紧跟…的名字表示元素(可变实参)
  5. [&,捕获列表] : 对于名字没有出现在捕获列表中的外层局部变量,通过引用方式捕获。列表的名字不能以&为前缀,捕获列表中的变量全部通过值的方式捕获
  6. [=,捕获列表] : 对于名字没有出现在捕获列表中的外层局部变量,通过值隐式捕获。捕获列表中不允许包含this,列出的名字必须以&为前缀。捕获列表中的变量名通过引用方式捕获。

注意 lambda的声明周期可能超过它的调用者,这个时候引用捕获可能造成垂悬指针。

1.3.1 lambda捕获引用对象垂悬示例

int TestLambdaCapture(std::function<int(void)> &lambdaObj)
{
int testval = 100;
std::function<void(void)> lambdaObj2 = [testval]() {
// error: cannot assign to a variable captured by copy in a non-mutable lambda
// testval = 200;
};
std::function<void(void)> lambdaObj1 = [testval]() mutable {
testval = 200;
};
lambdaObj = [&testval]() -> int {
return testval;
};
cout<<lambdaObj()<<endl;
return 0;
}

int main(int argc, char *argv[]) {
std::function<int(void)> lambdaObj;
TestLambdaCapture(lambdaObj);
cout<<lambdaObj()<<endl; // 这里会输出一个随机值,因为lambda捕获的变量已经被其它函数调用栈覆盖

int (*testFunPtr)(int) = [](int a) -> int { return a; };
}

1.4 返回值

lambda通过->符号注明返回值类型 比如 []()->int{} 返回int类型 相对于普通函数,lambda有两点需要注意

  1. 如果不接受任何参数,lambda的参数列表可以忽略,所以最简单的lambda表达式是 []{}
  2. lambda的返回类型可以通过lambda进行推断得到,但是情况不能太复杂

1.5 lambda的类型

lambda可以声明为一个auto或 std::function<R(AL)>类型的变量,其中R是lambda的返回类型,AL是他的参数列表。 当lambda未捕获任何外层变量的时候,他可以简单的通过一个相应类型的函数指针存储

1.6 lambda和this

当在类成员函数中使用lambda的时候可能希望访问类的成员变量,这个时候可以通过捕获this的方式实现,但是注意[this]和[=]互不兼容,且因为this是指针,所以成员变量全部是引用方式访问。

1.7 mutable的lambda

通过在函数列表后标注mutable可以限定lambda的函数体内是否可以对拷贝捕获的变量进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TestLambdaThisCap {
private:
int testa;
public:
std::function<int(void)> testfunc() { return [this]()->int { return testa; };}
};

int main(int argc, char *argv[]) {
TestLambdaThisCap test;
std::function<int(void)> testl = test.testfunc();
int reval = testl(); //注意这里调用testl的时机不能超出test对象的生命周期,否则this为垂悬指针

}

Last Updated 2018-01-07 Sun 19:58.
Emacs 24.5.1 (Org mode 9.0.9)