前言
之前在学muduo网络库时,看到陈硕以基于对象编程的方式,大量使用boost
库中的bind
和function
机制,如今,这些概念都已引入至C++11,包含在头文件<functional>
中。
本篇文章主要梳理C++绑定器相关的内容以及C++11中引入的function
机制,其中绑定器主要有三种:bind1st
、bind2nd
、bind
(C++11)。学完本篇内容,将对C++绑定器及function
机制等的底层实现有深刻理解,那么我们开始说吧。
函数对象
首先说说函数对象,之所以说函数对象,是因为绑定器、function
都涉及到该部分概念。函数对象实际上是类调用operator()()
小括号运算符重载,实现像在“调用函数”一样的效果,因此还有个别名叫“仿函数”。函数对象示例代码如下:
class Print {
public:
void operator()(string &s) { cout << s << endl; }
};
int main() {
string s = "hello world!";
Print print; //定义了一个函数对象print
print(s);
return 0;
}
上面代码print(s);
语句,看似像函数调用,其实是类对象print
调用其小括号运算符重载print.operator(string &s)
。print
就是一个函数对象,至此对函数对象就有了基本的认识。
剖析绑定器bind1st、bind2nd
了解了函数对象,接下来我们说说绑定器,为什么需要绑定器?在使用STL
时经常会遇到STL
算法中需要传递某元函数对象,比如在写sort
时,第三个参数决定了我们的排序规则,用来接收一个“比较器”函数对象,该函数对象是一个二元的匿名函数对象,形如greator<int>()
或者less<int>()
。二元函数对象的意思是,这个函数对象的小括号运算符重载函数接收两个参数,那么几元就表示接收几个参数。下面是库中自带的greater
和less
模板类的源码实现,可以看到是对小括号运算符重载的实现,sort
第三个参数接收该模板类的二元匿名函数对象。
template<typename _Tp>
struct greater : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x > __y; }
};
template<typename _Tp>
struct less : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x < __y; }
};
再回到刚才的问题,那为什么需绑定器?由于STL接口的限制,有时我们拿到的函数对象和特定STL算法中要接收的函数对象在参数上并不匹配,意思就是需要传递一个一元函数对象,你有一个二元函数对象,那可以通过绑定器提前绑定二元函数对象的其中一个参数,使得最终返回的是一个一元函数对象,那么从二元函数对象到一元函数对象的转换过程,就需要绑定器去实现。
如STL中的泛型算法find_if
,可用来查找可变长数组vector
中符合某个条件的值(这个条件比如是要大于50,要小于30,要等于25等等)。其第三个参数需要传递一个一元函数对象,假如现在要找到第一个小于70
的数,可将绑定器与二元函数对象结合,转换为一元函数对象后传递给find_if
。
我们知道系统自带的greater<int>()
和less<int>()
模板类对象是二元匿名函数对象,所以需要通过绑定器将其转换为一元函数对象,可以通过bind1st
和bind2nd
去绑定,顾名思义,前者对二元函数对象的第一个参数进行绑定,后者对二元函数对象的第二个参数进行绑定,两个绑定器均返回一元函数对象,用法如下:
sort(vec.begin(), vec.end(), greater<int>()); //从大到小对vector进行排序
find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70));
find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70));
两个绑定器分别提前绑定了一个参数,使得二元函数对象+绑定器转换为一元函数对象:
operator()(const T &val)
greater a > b ====> bind1st(greater<int>(), 70) ====> 70 > b
less a < b ====> bind2nd(less<int>(), 70) ====> a < 70
下面给出bind1st
绑定过程图,二元函数对象绑定了第一个数为70
,变为一元函数对象,传递给find_if
泛型算法,此时find_if
所实现的功能就是:找出有序降序数组中第一个小于70
的数,所以find_if
返回指向65
元素的迭代器:
file:///Users/guochen/Notes/docs/media/16656563650484/16657214749366.jpg
以上就是绑定器的概念。因此需要绑定器的原因就很明显了,绑定器可以返回一个转换后的某元函数对象,用于匹配泛型算法。
根据上面的理解,接下来实现一下bind1st
,代码实现如下:
/*可以看到 自己实现的绑定器本质上也是个函数对象 调用operator()进行绑定*/
template<typename Compare, typename T>
class _mybind1st {
public:
_mybind1st(Compare comp, T first) : _comp(comp), _val(first) {}
bool operator()(const T &second) {
return _comp(_val, second);
}
private:
Compare _comp;
T _val;
};
/*实现bind1st 函数模板*/
//直接使用函数模板,好处是可以进行类型推演
template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T &val) { //绑定器返回值_mybind1st为一元函数对象
return _mybind1st<Compare, T>(comp, val);
}
上述代码中mybind1st
绑定器第一个参数Compare comp
是要绑定的二元函数对象,第二个参数val
是在原有函数对象上绑定的值,最后绑定器调用_mybind1st
模板函数对象的小括号运算符重载并返回该一元匿名函数对象,可以看到_mybind1st
小括号运算符重载中已将绑定器mybind1st
第二个参数val
传递给了原本的二元函数对象Compare comp
,因此原本绑定器接收的二元函数对象只需要处理第二个参数。所以绑定器返回的函数对象_mybind1st
其实是在原本的函数对象上套了一层参数的新的函数对象,阅读上面的代码实现,就可更深刻的理解bind1st
的底层原理。
与此同时,不难写出bind2nd
的实现,顾名思义该绑定器是对第二个参数进行绑定,不过多赘述,贴出实现代码:
template<typename Compare, typename T>
class _mybind2nd {
public:
_mybind2nd(Compare comp, T second) : _comp(comp), _val(second) {}
bool operator()(const T &first) {
return _comp(first, _val);
}
private:
Compare _comp;
T _val;
};
template<typename Compare, typename T>
_mybind2nd<Compare, T> mybind2nd(Compare comp, const T &val) {
return _mybind2nd<Compare, T>(comp, val);
}
根据上文,我们清楚了解到泛型算法find_if
第三个参数接收一元函数对象,且该泛型算法功能是寻找第一个符合某条件的元素,我们对其补充实现,代码贴出:
/**
* 自己实现了find_if后发现其实绑定器返回的就是绑定后的函数对象
* 使用绑定器的目的:就是将原本某元的函数对象转化为另一个元的函数对象
* 说白了,绑定器还是对函数对象的一个应用
**/
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp) {
for(; first != last; ++first) {
if(comp(*first)) { //调用comp的小括号运算符重载 一元函数对象 comp.operator()(*first)
return first;
}
}
return last;
}
此时要寻找vector
中第一个小于70
的数,就可以这样写:
auto it = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), 70));
cout << *it << endl; //打印vec中第一个小于70的数值
以上,围绕bind1st
、bind2nd
以及函数对象等,展开讨论了绑定器bind1st
、bind2nd
的实现原理,但是同时我们也发现其缺点,就是只能对二元函数对象进行绑定转换,让其转换为一元函数对象,那如果遇到很多元的函数对象,我们还得一个一个自己去实现吗?所以将boost库的boost::bind
引入到了C++11标准库中,接下来我们介绍C++11的绑定器std::bind
,它是对上述两种绑定器的泛化。支持任意函数对象(其实标准库中最多支持29
元函数对象,不过这也足够使用了)。
补充:上面都是以函数对象为例,作为绑定器第一个参数传递,其实第一个参数可以是函数对象、成员函数、也可以是普通函数。
总结:绑定器本身是函数模板,绑定器第一个参数可能是普通函数、成员函数或函数对象等,返回的一定是函数对象。还有就是这两个绑定器在C++17
中已移除,因此仅用于学习和理解绑定器,也方便我们对C++11引入的bind
的学习。至于当前这两个绑定器如何实现对类成员函数的绑定等等我们也没必要去寻找答案了(我一开始也在努力寻找如何使用这两个绑定器去绑定类成员函数,但是发现bind
可以很轻松地做到,当然如果大家知道怎么使用bind1st
和bind2nd
绑定类成员函数,也可以评论告知我,感谢~)。
C++11 bind通用绑定器(函数适配器)
我们可将bind
函数看作是一个通用的函数适配器,它接受一个可调用函数对象,生成一个新的可调用函数对象来“适应”原对象的参数列表。bind相比于bind1st和bind2nd,实现了“动态生成新的函数”的功能。简言之,可通过bind
函数修改原函数并生成一个可以被调用的对象,类似于函数的重载,但是我们又不需要去重新写一个函数,用bind
函数就可以实现。相信在上面讲bind1st和bind2nd时,大家对这些关于绑定器(函数适配器)的概念已经有所认知,我们直接看看如何用的吧。
绑定一个普通函数和函数指针
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;
int fun(int a, int b, int c, int d, int e) {
return a + b - c + d - e;
}
int main() {
int x = 1, y = 2, z = 3;
auto g = bind(fun, x, y, _2, z, _1); //第一个参数&可省略 但最好写成&fun
cout << g(11, 22) << endl; // fun(1, 2, 22, 3, 11) => 1+2-22+3-11
// cout << bind(fun, x, y, _2, z, _1)(11, 22) << endl; //等价
}
g
是有两个参数的二元函数对象,其两个参数分别用占位符placeholders::_2
和placeholders::_1
表示,_2
代表二元函数对象的第二个参数22
,_1
代表二元函数对象的第一个参数11
。这个新的可调用对象将它自己的参数作为第三个和第五个传递给fun,fun函数的第一个、第二个第四个参数分别被绑定到给定的值x
、y
、z
上。
绑定一个类的静态成员函数与绑定全局函数没有任何区别,这里不做说明,可参考文章:[
标签:11,function,函数,int,bind,绑定,对象,参数 From: https://www.cnblogs.com/S1mpleBug/p/16793438.html