C++11匿名函数

本文记录了匿名函数基本使用,并且引入函数指针、回调函数相关知识,介绍如何简洁地在C++11后使用function、bind等,在普通、成员函数实现回调,并且解决以往在仿函数、匿名函数函数指针无法实现问题。

定义一个匿名函数:

1
auto add = [](int a,int b)->int{return a+b;};

左侧是自动推导类型和函数名,右侧结构是:

1
2
3
4
[捕获外部变量](参数列表)->返回值类型{函数体}

//其中"->返回值类型均可省略",因为匿名函数的返回值可以直接推导,因此:
auto add = [](int a,int b){return a+b};

捕获外部变量

匿名函数的基本结构和普通函数都是类似的,例如参数列表、函数体、函数名等,优化了自动推导类型,需要理解的只有这个外部变量的捕获,因为匿名函数的产生,C++函数更容易写出嵌套写法,因此有了捕获外部变量的问题,常见的是来自外层函数的局部变量等。

基本含义标识是:

1
2
3
4
5
6
7
8
9
10
[] -- 不捕获外部变量
[&] -- 以引用的方式捕获外部变量
[=] -- 以值副本的方式捕获
[a,b...] -- 以值方式捕获a、b等
[&a,&b...] -- 以引用方式捕获a、b等
[this] -- 常用于成员函数中的匿名函数,可方便调用成员函数和成员变量

//混合类型
[=,&a] -- 仅变量a以引用传递
[&,a] -- 仅变量a以值传递

引用传递绑定的是变量地址,函数调用前变量发生改变,函数调用时结果也会改变;而值传递在定义时已经给定了值副本,因此后面捕获的变量即使变化了,和副本也无关了:

1
2
3
4
5
6
7
8
int main(){
int a = 100;
auto add = [=](int b)->int{return a+b;}; //值捕获,cout << add(20)输出120
//auto add = [&](int b)->int{return a+b;};//引用捕获,cout << add(20)输出220
a = 200;
cout << add(20);
return 0;
}

而且值传递的变量不支持进行操作,引用则可以:

1
2
3
4
5
6
7
int main(){
int a = 100;
auto add = [&a]()->int{a++;return ++a;}; //使用[a]将报错
cout<<add(); //102
cout<<a; //102
return 0;
}

std::function和std::bind

匿名函数的引入使得C++函数定义可以和Python等一样,可以有多种模板和定义实现,调用防守基本比较千篇一律,类似函数指针,C++11引入了std::function和std::bind,除了管理普通函数和成员函数,还可以对匿名函数、仿函数等对象进行保存和调用。

从普通函数指针引入

先回顾普通函数指针的方法,与C兼容,以下分别面对回调函数、成员函数:

1
2
3
4
5
6
7
8
9
10
11
int callback(int a,int b){
return a+b;
}
void test(int (*callback)(int,int)){ //回调函数作参数
int a = 30;
cout<<callback(a,70);
}
int main(){
test(callback);
return 0;
}

使用函数指针分别管理非静态成员函数、静态成员函数:

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
#include <iostream>
using namespace std;

class Person{
public:
Person(){}
~Person(){}
void printInfo(int a){ //非静态
cout<<a<<endl;
}

static void stc_print(int a){ //静态
cout<<a<<endl;
}
};

int main(){
//平平无奇函数调用
Person p1;
int a = 100;
p1.printInfo(a);

//类对象通过非静态成员函数指针调用函数
void (Person::*foo)(int) = &Person::printInfo;
(p1.*foo)(a);

//同前,指针类对象调用而已
Person *p2 = new Person();
(p2->*foo)(a); //必须指明哪个对象调用的
delete p2;

//类调用静态成员函数指针,静态成员函数函数指针无需显式指明类类型、类对象
void (*foo1)(int) = &Person::stc_print;
foo1(a);

return 0;
}
最后再进一步,我们希望基于此实现成员函数的回调,对于静态类成员,实现是显而易见的:
1
2
3
4
5
6
7
void test1(void (*foo1)(int)){ //定义回调函数结构
int a = 1000;
foo1(a);
}

//main函数调用
test1(Person::stc_print);
此时对于非静态成员函数,情况就稍微复杂了些,例如通过参数将类对象传递进去,对于仿函数、匿名函数,函数指针就无能为力了,因此就有必要了解function和bind基本使用。

std::function与bind

定义了五种函数实现a+b的写法,并且通过function回调函数调用,以下:其中普通函数、匿名函数、静态成员函数均无需对象调用,因此使用赋值语句即可实现绑定,而非静态成员函数、仿函数需要实例化对象,可以通过bind实现。

function包含头文件《functional》,基本结构是function<返回类型(参数列表)>指针名称 = 回调函数函数名;bind的作用是将对象和参数绑定在一起,掌握以下写法即可。

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
53
54
55
56
57
#include <iostream>
#include <functional>
using namespace std;

//普通函数
int callback1(int a,int b){
return a+b;
}

//仿函数
struct callback2{
int operator()(int a,int b){
return a+b;
}
};

//成员函数
class Accumulate{
public:
int callback3(int a,int b){
return a+b;
}
static int callback4(int a,int b){
return a+b;
}
};

int main(){
//匿名函数
auto callback5 = [](int a,int b){return a+b;};

//function方法
//普通函数
function<int(int,int)>test1 = callback1;
cout<<test1(1,1);

//仿函数
callback2 callback2; //实例化结构体
function<int(int,int)>test2 = callback2;
//function<int(int,int)>test2 = bind(callback2,1,1); //bind方法
cout<<test2(1,1);

//非静态成员函数
Accumulate acc;
function<int(int,int)>test3 = bind(Accumulate::callback3,&acc,1,1);
cout<<test3(1,1);

//静态成员函数
function<int(int,int)>test4 = Accumulate::callback4;
cout<<test4(1,1);

//匿名函数
function<int(int,int)>test5 = callback5;
cout<<test5(1,1);

return 0;
}

当使用bind方法时,参数的绑定是自由的,上面test3绑定了参数,我们实际调用时就可以省略调用了,修改参数列表即可:

1
2
function<int(void)>test3 = bind(Accumulate::callback3,&acc,1,1); //参数列表缺省
cout<<test3(); //无需参数

如果有的需要提前绑定,有的需要调用时确定,就可以使用std::placeholders + _x方法,代表第n个参数由调用时参数列表的第x个参数决定,n就是placeholders所在的参数位置。

1
2
function<int(int)>test3 = bind(Accumulate::callback3,&acc,1,std::placeholders::_1); //第二个参数由调用的第一个参数决定
cout<<test3(1); //无需参数

此外,函数指针无效时为null,function保存指针无效时会扔出错误。