C++11/14 新特性(function/bind 可调用对象包装器与绑定器)

1 可调用对象

在 C++ 中,可调用对象一般是指:

  • 一个函数指针

  • 一个重载 () 操作符的类对句(仿函数)

  • 一个可被转换为函数指针的类对象

  • 一个类成员函数指针

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
58
59
60
#include <iostream>

void func(void)
{
std::cout<<"func(void) called"<<std::endl;
}

struct Foo
{
void operator()(void)
{
std::cout<<"Foo::()(void) called"<<std::endl;
}
};

struct Bar
{
using ft_t = void(*)(void);
static void func(void)
{
std::cout<<"Bar::func(void) called"<<std::endl;
}

operator ft_t(void)
{
return func;
}
};

struct A
{
int m_a;
void mem_func(void)
{
std::cout<<"A:memfunc(void) called and m_a="<<m_a<<std::endl;
}
};

int main() {
//1.函数指针
void(* func_ptr)(void) = &func;
func_ptr();

//2.仿函数
Foo foo;
foo();

//3.可被转换为函数指针的类对象
Bar bar;
bar();

//4.类成员函数指针或类成员指针
void (A::*mem_func_ptr)(void) = &A::mem_func;
int A::*mem_obj_ptr = &A::m_a;
A aa;
(aa.*mem_obj_ptr) = 123;
(aa.*mem_func_ptr)();

return 0;
}

上例中的这些对象(func_ptr,foo,bar,mem_func_ptr,mem_obj_ptr) 均可称之为 “可调用对象”。相应的,其类型可被称作“可调用类型”。注意这里只有成员函数有成员函数指针而没有函数类型或函数引用类型,这是因为函数类型并不能直接用于定义对象,而函数引用或以看做一个 const 的函数指针。 可调用对象具有比较统一的调用形式,即使用括号操作(除成员函数指针),而定义的方法各不一样。这样我们在试图使用统一的方式保存,或传递一个可调用对象时,会十分烦琐。

2 std::function 可调用对象包装器

std::function 是一个类模板,它可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟调用它们。

见代码:

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
#include <iostream>
#include <functional>

void func(void)
{
std::cout<<__FUNCTION__<<std::endl;
}

class Foo
{
public:
static int foo_func(int a)
{
std::cout<<__FUNCTION__<<std::endl;
return a;
}
};

class Bar
{
public:
int operator()(int a)
{
std::cout<<__FUNCTION__<<std::endl;
return a;
}
};

int main(void)
{
//绑定普通函数
std::function<void(void)> fr1 = func;
fr1();

//绑定类的静态成员函数
std::function<int(int)> fr2 = Foo::foo_func;
std::cout<<fr2(112)<<std::endl;

//绑定仿函数
Bar bar;
fr2 = bar;
std::cout<<fr2(221)<<std::endl;

return 0;
}

从上面的例子可以看到,当给 std::function 填入适当的函数类型(即返回值和参数形参列表)之后,它就变成了一个可以容纳这一类调用方式的“函数包装器”。

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
class Progresser
{
public:
Progresser(const std::function<void(float)>& func):m_callback(func)
{}

void notify(float progress)
{
m_callback(progress);
}
private:
std::function<void(float)> m_callback;
};

class Logic
{
public:
void operator()(float val)
{
std::cout<<(val * 100)<<"%\t";
}
};
int main(void)
{

Logic logic;
Progresser pro(logic);
for(float f = 0.0; f<1; f+=0.2)
{
pro.notify(f);
}
}

在这里, std::function 可以取代函数指针的作用。它可以保存函数的延迟执行,可以把它看作 C# 中特殊的委托。 std::function 还可以作为函数的入参数:

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
#include <vector>
template<typename T>
void my_foreach(const std::vector<T>& arr,const std::function<void(T)>& f)
{
for(auto n : arr)
{
f(n);
}
}

template <typename T>
void outputElement(const T& n)
{
std::cout<<n<<" ";
}

int main(void)
{
std::vector<int> arr = {1,2,3,4,5};
std::function<void(int)> fr = outputElement<int>;
my_foreach(arr, fr);

std::vector<char> arrc = {'A','B','C'};
std::function<void(char)> frc = outputElement<char>;
my_foreach(arrc, frc);
}

上例中使用 std::function 实现了简易的 for_each 函数。

3 std::bind 绑定器

std::bind 可以将可调用对象与其参数一起进行绑定,产生一个新的可调用对象。在 C++98 中,已经有了 std::bind1st 和 std::bind2nd 用来绑定 functior 的两个参数,而C++11 中提供了 std::bind 。bind 是一种延迟调用的思想, 一般它有两大作用:

  • 将可调用对象与其参数一起绑定成一个仿函数

  • 为函数对象降元。可将多元函数对象转换成一元或少元可调用对象。即只绑定部分参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <functional>

void Func(int n, char c, float f)
{
std::cout<<"n:"<<n;
std::cout<<"\tc:"<<c;
std::cout<<"\tf:"<<f;
std::cout<<std::endl;
}

int main()
{
std::function<void(int)> bindFunc1 = std::bind(Func,std::placeholders::_1,'P',3.14);
bindFunc1(1024);

auto bindFunc2 = std::bind(Func,std::placeholders::_2,std::placeholders::_3,std::placeholders::_1);
bindFunc2(3.14,1024,'P');

return 0;
}

bind 能够在绑定的同时绑定一部分参数,而未提供的参数则使用占位符表示。代码里的 bindFunc2 实际应为 std::function<void(int,float,char)> 类型。 绑定的参数以值传递的方式传递给具体函数,而占位符则会使用引用传递