文章

C++11中引入的bind绑定器和function函数对象

C++语法我是真的弱,所以这一段再把基础知识学一学吧,因为本来要做项目的,但是总是被语法给难倒,果真这还是基础和内功

1.bind1st和bind2nd什么时候会用到

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
61
62
#include <iostream>
#include <vector>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>

using namespace std;
template <typename Container>
void showContainer(Container& con)
{
	typename Container::iterator it = con.begin();
	for (; it < con.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;
}

using namespace std;
int main()
{
	vector<int> vec;
	srand(time(nullptr));
	for (int i = 0; i < 20; i++)
	{
		vec.push_back(rand() % 100 + 1);
	}
	showContainer(vec);
	sort(vec.begin(), vec.end()); //默认从小到大排序
	showContainer(vec);

	//greater 二元函数对象
	//一般排序默认都是less,这个可以看源代码
	sort(vec.begin(), vec.end(), greater<int>()); //大到小
	showContainer(vec);

	/*
	把70按顺序插入到vec容器中,找第一个小于70的数字
	operator()(const T &val)
	greater a > b;
	less a < b;
	绑定器 + 二元函数对象 => 一元函数对象
	bind1st: + greater bool operator()(70, const _Ty& _Right)
	bind2nd: + less bool operator()(const _Ty& _Left, 70)
	*/
	auto it1 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70));
	//或者这样也行
	auto it1 = find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70));
	/*
	它使用了bind2nd函数适配器来将二元函数对象less<int>转换为一个一元谓词。
	less<int>是一个二元函数对象,它接受两个int类型的参数并返回它们的比较结果(即第一个参数是否小于第二个参数)。
	而bind2nd(less<int>(), 70)则将less<int>的第二个参数绑定为常量70
	从而得到一个一元谓词,它接受一个参数并返回该参数是否小于70。
	这是C++98中的,现在已经舍弃了
	*/
	if (it1 != vec.end())
	{
		vec.insert(it1, 70);
	}
	showContainer(vec);
}

2.bind1st和bind2nd底层原理

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <vector>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>

using namespace std;
template <typename Container>
void showContainer(Container& con)
{
	typename Container::iterator it = con.begin();
	for (; it < con.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;
}

template <typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp)
{
	for (; first != last; first++)
	{
		if (comp(*first))	//com.opertor()(*first)相当于调用了小括号运算符重载
		{
			return first;
		}
	}
	return last;
}

template<typename Compare, typename T>
class _mybind1st // 绑定器是函数对象的一个应用
{
public:
	_mybind1st(Compare comp, T val)
		:_comp(comp), _val(val)
	{}
	bool operator()(const T& second)
	{
		return _comp(_val, second); // greater
	}
private:
	Compare _comp;
	T _val;
};

//mybind1st(greater<int>(), 70)
template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T& val)
{
	//直接使用函数模板,好处是,可以进行类型的推演
	return _mybind1st<Compare, T>(comp, val);
}




using namespace std;
int main()
{
	vector<int> vec;
	srand(time(nullptr));
	for (int i = 0; i < 20; i++)
	{
		vec.push_back(rand() % 100 + 1);
	}
	showContainer(vec);
	sort(vec.begin(), vec.end()); //默认从小到大排序
	showContainer(vec);

	//greater 二元函数对象
	//一般排序默认都是less,这个可以看源代码
	sort(vec.begin(), vec.end(), greater<int>()); //大到小
	showContainer(vec);

	/*
	把70按顺序插入到vec容器中,找第一个小于70的数字
	operator()(const T &val)
	greater a > b;
	less a < b;
	绑定器 + 二元函数对象 => 一元函数对象
	bind1st: + greater bool operator()(70, const _Ty& _Right)
	bind2nd: + less bool operator()(const _Ty& _Left, 70)
	*/
	auto it1 = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), 70));
	//或者这样也行
	//auto it1 = my_find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70));
	/*
	它使用了bind2nd函数适配器来将二元函数对象less<int>转换为一个一元谓词。
	less<int>是一个二元函数对象,它接受两个int类型的参数并返回它们的比较结果(即第一个参数是否小于第二个参数)。
	而bind2nd(less<int>(), 70)则将less<int>的第二个参数绑定为常量70
	从而得到一个一元谓词,它接受一个参数并返回该参数是否小于70。
	这是C++98中的,现在已经舍弃了
	*/
	if (it1 != vec.end())
	{
		vec.insert(it1, 70);
	}
	showContainer(vec);
}

3.function函数对象类型的应用示例

3.1.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
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <vector>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>

using namespace std;

void hello1()
{
	cout << "hello" << endl;
}

void hello2(string s)
{
	cout << s << endl;
}

int sum(int a, int b)
{
	cout << a + b << endl;
	return a + b;
}

class Test
{
public:	//类的成员函数调用必须依赖一个对象void (Test::::*pfunc)(string)
	void hello(string str) { cout << str << endl; }
};
int main()
{
	// 从function的类模板定义处,看到希望用一个函数类型实例化function
	function<void()> func1 = hello1;
	func1();
	function<void(string)> func2 = hello2;
	func2("hello2");
	function<int(int, int)> func3 = sum;
	func3(1, 2);

	// operator()
	function<int(int, int)> func4 = [](int a, int b)->int {return a + b; };
	cout << func4(100, 200) << endl;

	// 因为成员函数的参数里面都有用一个指向自己的指针,也就是Test*
	function<void(Test*, string)> func5 = &Test::hello;
	// 对于成员方法的调用,是要指向一个对象的,也就是&Test()
	Test t;
	func5(&t, "hello test!");
	return 0;
}
// 总结
//1.function<函数类型>,用函数类型实例化function
//2.function<函数类型(参数)>  func1  使用时候也是得func1(参数)

3.2.function 的好处

那说了这么多,function的好处到底是哪里呢,为什么我要这样子干,而不是直接调用函数呢

好处就是它能把看到的各种类型保存下来

比如

func1 = hello1;

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

func5 = &Test::hello;

它能把函数,lambda表达式,成员函数的类型保存下来

举个例子,比如图书馆里系统图

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
int main()
{
	int choice = 0;
	for (;;)
	{
		cout << "----------------" << endl;
		cout << "1.查看所有书籍信息" << endl;
		cout << "2.借书" << endl;
		cout << "3.还书" << endl;
		cout << "4.查询书籍" << endl;
		cout << "5.注销" << endl;
		cout << "----------------" << endl;
		cout << "请选择:" << endl;
		cin >> choice;
	}
	switch (choice)
	{
	case 1:
		break;
	case 2:
		break;
	case 3:
		break;
	case 4:
		break;
	case 5:
		break;
	default:
		break;
	}

}

但是这个代码不好,因为无法闭合,无法做到开闭原则,每次新增加一个功能 都要把switch里面的都改一下

我们可以把每件事情的功能都封装到一个函数里

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
void doShowAllBook() { cout << "查看所有书籍信息" << endl; }
void doBorrow() { cout << "借书" << endl; }
void doBack() { cout << "还书" << endl; }
void doQueryBooks() { cout << "查询书籍" << endl; }
void doLoginOut() { cout << "注销" << endl; }
int main()
{
	int choice = 0;
	map <int, function<void()>> actionMap;
	actionMap.insert({ 1, doShowAllBook });
	actionMap.insert({ 2, doBorrow });
	actionMap.insert({ 3, doBack });
	actionMap.insert({ 4, doQueryBooks });
	actionMap.insert({ 5, doLoginOut });

	for (;;)
	{
		cout << "----------------" << endl;
		cout << "1.查看所有书籍信息" << endl;
		cout << "2.借书" << endl;
		cout << "3.还书" << endl;
		cout << "4.查询书籍" << endl;
		cout << "5.注销" << endl;
		cout << "----------------" << endl;
		cout << "请选择:" << endl;
		cin >> choice;
		auto it = actionMap.find(choice);
		if (it == actionMap.end())
		{
			cout << "failed" << endl;
		}
		else
		{
			it->second();
		}
	}
}

有人说那函数指针也可以做到呀,但是我们这里只是用了函数,那要是有lambda,有函数绑定怎么办呢,对吧,所以还是用function靠谱,可以返回任何的类型。

像lambda表达式只能作用在语句中,而有了function就可以随心所欲的用了,要不然就得重新写表达式或者重新绑定了

4.模板的完全特例化和部分特例化

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
bool compare(T a , T b)
{
	//cout << "template compare" << endl;
	return a > b;
}

int main()
{
	compare(10, 20);
	compare("aa", "bb");
	return 0;
}

像这段代码,10,20可以比较,但是aa和bb不能比较,到时候传入的只是他们的地址

所以编译器自动推演的类型,不符合我们实际处理的逻辑,所以我们需要自己特例化下

所以需要

1
2
3
4
5
template<>
bool compare<const char*>(const char* a, const char* b)
{
	return strcmp(a, b) > 0;
}

这样才能比较,这叫做完全特例化,因为你看所有的都是compare<const char*>是完全已知的

template<> 是一个模板特化的语法。它允许你为特定的模板参数类型提供特定的实现

strcmp 是一个 C 标准库函数,用于比较两个 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
61
62
63
64
65
66
template<typename T>
class Vector
{
public:
	Vector() { cout << " call Vecotr template init" << endl; }
};

// 完全特例化,因为template<>
template<>
class Vector<char*>
{
public:
	Vector() { cout << " call Vecotr<char*> template init" << endl; }
};

// 我们知道要传入一个指针类型,但不知道具体是什么指针,所以用部分特例化
template<typename Ty>// 这是正常
class Vector<Ty*> // 在这里具体下,所以叫做部分特例化
{
public:
	Vector() { cout << " call Vecotr<Ty*> template init" << endl; }
};

// 部分特例化
// 指针函数指针,(有一个返回值,有两个形参变量)提供的部分特例化
template<typename R, typename A1, typename A2>
class Vector<R(*)(A1, A2)>
{
public:
	Vector<R(*)(A1, A2)>() { cout << " call Vecotr<R(*)(A1, A2)> template init" << endl; }
};

//函数类型部分特例化,(有一个返回值,有两个形参变量)
template<typename R, typename A1, typename A2>
class Vector<R(A1, A2)>
{
public:
	Vector<R(A1, A2)>() { cout << " call Vecotr<R(A1, A2)> template init" << endl; }
};

int sum(int a, int b) { return a + b; }

int main()
{
	Vector<int> vec1;
	Vector<char*> vec2;
	Vector<int*> vec3;
	Vector<int(*)(int, int)> vec4;//函数指针类型
	Vector<int(int, int)> vec5;//函数类型

	// 注意区分函数类型和函数指针类型
	// 函数指针类型
	typedef int(*Test)(int, int);
	Test pfunc = sum;
	cout << pfunc(10, 20) << endl;

	// 函数类型
	typedef int Test1(int, int); 
	Test1 *pfunc1 = sum; 
	cout << pfunc1(10, 20) << endl;
	// 表示 pfunc1 是一个指向 Test1 类型函数的指针,它被初始化为指向 sum 函数。
	// 由于函数名会自动转换为指向该函数的指针,因此可以直接将 sum 赋值给 pfunc1
	// 但是,由于 pfunc1 是一个指针,所以需要在其类型前加上 * 来表示它是一个指针。

	return 0; 
}

5.模板的实参推演

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
void func1(T) { cout << typeid(T).name() << endl; }

int sum(int a, int b) { return a + b; }

int main()
{
	func1(10);
	func1("aaa");
    func1(sum);
	return 0; 
}

输出结果是

1
2
3
int
char const * __ptr64
int (__cdecl*)(int,int)

但是目前的问题是T是把所有的类型全一股脑搞出来了,那如果我们只想让他一个个出来怎么办呢

也有办法,看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<typename T>
void func1(T a) { cout << typeid(T).name() << endl; } 

template<typename R, typename A, typename B>
void func2(R(*a)(A, B))
{
	cout << typeid(R).name() << endl;
	cout << typeid(A).name() << endl;
	cout << typeid(B).name() << endl;
}

int sum(int a, int b) { return a + b; }

int main()
{
	func1(10);
	func1("aaa");
	func1(sum);
	func2(sum);
	return 0; 
}

上面的a写不写无所谓的,反正只是表明是T类型而已的一个形参

输入结果为

1
2
3
4
5
6
int
char const * __ptr64
int (__cdecl*)(int,int)
int
int
int

这样的话sum的各个小块类型我们也可以得到,通过特例化

甚至我可以用下面的

1
2
3
4
5
6
class Test
{
public:
	int sum(int a, int b) { return a + b; }
};
func1( &Test::sum);

输出为

1
int (__cdecl Test::*)(int,int) __ptr64

也是个大类型,那我们怎么把它拆分下呢,写个func3

1
2
3
4
5
6
7
8
9
template<typename R, typename T,typename A, typename B>
void func3(R(T::*a)(A, B))
{
	cout << typeid(R).name() << endl;
	cout << typeid(T).name() << endl;
	cout << typeid(A).name() << endl;
	cout << typeid(B).name() << endl;
}
func3(&Test::sum);

输出为

1
2
3
4
int
class Test
int
int

所以模板实参可以把复杂类型一个个取出来,这就是它的魅力所在

6.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
void hello(string str) { cout << str << endl; }
int sum(int a, int b) { return a + b ; }
//类模板的声明
template<typename Fty>
class myfunction{};

template<typename R, typename A1>
class myfunction<R(A1)>
{
public:
	using PFUNC = R(*)(A1);
	myfunction(PFUNC pfunc) : _pfunc(pfunc){}
	R operator()(A1 arg)
	{
		return _pfunc(arg);
	}
private:
	PFUNC _pfunc;
};

template<typename R, typename A1, typename A2>
class myfunction<R(A1, A2)>
{
public:
	using PFUNC = R(*)(A1, A2);
	myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
	R operator()(A1 arg1, A2 arg2)
	{
		return _pfunc(arg1, arg2);
	}
private:
	PFUNC _pfunc;
};


int main()
{
	myfunction<void(string)> func1(hello);
	func1("hello world!"); //func1.operator()("hello world!")
	myfunction<int(int, int)> func2(sum);
	cout << func2(10, 20) << endl;
	return 0;
}

有人会问,那总不能每次是一个新的函数,就重新写一个模板类吧

当然不会,咱们看R也就是void int,这个是确定的,一个R就可以表示,只不过是函数的参数有的是一个有的是两个,目前解决这个问题就行

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename R, typename... A> //这表示A不是一个类型,是一组类型,一堆一堆的
class myfunction<R(A...)> //这表示形参的个数是可变的
    {
public:
	using PFUNC = R(*)(A...);
	myfunction(PFUNC pfunc) : _pfunc(pfunc){}
	R operator()(A... arg)
	{
		return _pfunc(arg...); //这表示一组参数
	}
private:
	PFUNC _pfunc;
};

把这个替换掉刚才的两个没有一点问题

这就是function底层实现的原理

…这个是C++11提供的可变参类型参数

其中上面的类模板声明不能删除,如果删除了 template<typename Fty> class myfunction{}; 这一行代码,那么在定义特化版本的 myfunction 类模板时,编译器将无法找到 myfunction 类模板的原始声明。这将导致编译错误。因此,即使没有直接使用 template<typename Fty> class myfunction{}; 这个类模板,它仍然是必需的,因为它为特化版本的 myfunction 类模板提供了原始声明。

7.Bind

std::bind绑定器,也是个类模板,C++11引入的

std::bind能够将对象以及相关的参数绑定到一起,绑定完后可以直接调用,也可以用std::function进行保存,再需要的调用

格式:

std::bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值1,参数绑定值2…参数绑定值n)

总结:

a)将可调用对象和函数绑定在一起,构成一个仿函数,所以可以直接调用

b)如果函数有多个参数,可以绑定一部分参数,其他参数在调用的时候指定

直接调用

1
2
3
4
5
6
void hello(string str) { cout << str << endl; }

int main()
{
	std::bind(hello, "china")();
}

间接调用

1
2
3
4
5
6
7
void hello(string str) { cout << str << endl; }

int main()
{
	auto test = std::bind(hello, "china");
	return 0;
}

占位符placeholders::_1表示这个位置(当前placeholders::_1)将在函数调用时,被传入的第一个参数

本文由作者按照 CC BY 4.0 进行授权