STL中伪函数、函数对象(functor)初步理解(下)

来源:互联网 发布:c ide 知乎 编辑:程序博客网 时间:2024/05/22 13:48

好了,有了以上理论知识,就可以探讨一下函数对象的用法了,接下来我会以STL中使用最频繁的for_each做例子,来说明函数对象的原理和作用。

先查一下for_each的用法,在www.cplusplus.com上查到了比较详细的解释,原文如下:


Apply function to range

Applies function f to each of the elements in the range [first,last).

The behavior of this template function is equivalent to:


template<class InputIterator, class Function> Function for_each(InputIterator first, InputIterator last, Function f) { for ( ; first!=last; ++first ) f(*first); return f; }

Parameters

first, last
Input iterators to the initial and final positions in a sequence. The range used is [first,last), which contains all the elements between first and last, including the element pointed by first but not the element pointed by last.
f
Unary function taking an element in the range as argument. This can either be a pointer to a function or an object whose class overloads operator().
Its return value, if any, is ignored.

Return value

The same as f.


从上面这段文字首先可以看出,for_each是个区间操作函数,输入的前两个参数是起始迭代器和终点迭代器,而第三个参数则是一个function类型的参数,从下面的解释不难看出,这个参数既可以是函数,又可以是函数对象(也就是重载了()的类),也就是说,虽然函数对象在STL中很多地方已经取代了函数指针,但并不是函数指针就被完全抛弃了,因此,for_each所操作的对象有两种:函数对象和全局函数指针,以此类推,STL中所有其他range function也都具有相似的特征呢?很有可能,而事实上,几乎所有区间操作函数确实也全都是这样的。


好,有了以上的解释,我们就可以试试for_each的用法了。先从最简单的情况开始,试一试一元全局函数+函数指针的情况,看看以下代码片段。

#include <iostream>      #include <vector>      #include <iostream>      #include <algorithm>          using namespace std;          void printTest(const int &data)      {          cout << data << endl;      }          int main()      {          int test[] = {1, 2 , 3 , 4 , 5};          vector<int> v(test, test + sizeof(test) / sizeof(int));              for_each(v.begin(), v.end(), printTest);              return 0;      


这个代码片段中,给for_each传的参数是全局函数指针,可以通过编译,这也说明了STL可以接受正确的函数指针作为参数,那么,可不可以把这个函数指针转化成functor?当然可以,而使用的方法正是之前提到的,用ptr_fun来转,也就是这样:

for_each(v.begin(), v.end(), ptr_fun(printTest));

view 

仍然可以通过编译,也就是说,正确的全局函数指针转化成函数符是没有问题的。OK,下面试一下binary glocal function的情况,把原代码做一下微调,变成下面的样子:

#include <iostream>  #include <vector>  #include <iostream>  #include <algorithm>    using namespace std;    void printTest(const int &data, const int &num)  {      int result = data+num;      cout << "data:"<<data<<"num:"<<num<<",result:"<<result << endl;  }    int main()  {      int test[] = {1, 2 , 3 , 4 , 5};      vector<int> v(test, test + sizeof(test) / sizeof(int));        for_each(v.begin(), v.end(), printTest);        return 0;  }  

变化的部分是printTest函数,由unary function变成了binary function,主函数不变,编译出错,出错的原因很简单,对于带两个形参的全局函数,如果直接把函数指针传给for_each,程序不会知道哪个形参应该接收变量,因此会出错,正确的做法应该是先绑定参数,然后再传给for_each即可,这里,就用到了STL里的参数绑定器,也就是bind1st和bind2nd,拿bind1st做例子,这个函数的声明是这样的:

template <class Operation, class T> binder1st<Operation> bind1st (const Operation& op, const T& x);

再看一下对于参数的描述:

op

      Binary function object derived from binary_function.

x

      Fixed value for the first parameter of op.

第一个参数op是我们需要的operation,而第二个参数则是需要绑定的参数的值,也就是说,按照这种方式,如果我想在上面的代码中调用bind1st应该是类似这种形式:bind1st(printTest , 3),但是很明显,直接这么用肯定是错的,因为printTest并不是一个继承自binary_function的函数,这里就用到了刚才所用到的ptr_fun了,这个函数可以把global function转化成一个函数对象,而对于有两个参数的函数对象来说,则会返回一个继承自binary_function的函数对象,因此,对于这个程序,正确的绑定应该是这样:bind1st(ptr_fun(printTest) , 3),在这个表达式里,printTest的第一个参数被绑定成了3,好,再次编译,因该没问题了吧?错了,仍然有问题,问题如下:

error C2535: 'void std::binder1st<_Fn2>::operator ()(const int &) const' : member function already defined or declared.

函数存在重复定义,而重复部分函数在STL中的xFunctional头文件中,重复的函数为:

std::pointer_to_binary_function<const int &,const int &,void,void (__cdecl *)(const int &,const int &)>

这里我们知道,ptr_fun可以把全局函数转换成一个pointer_to_binary_function,但是该函数的默认参数正式和printTest具有一样形式的函数,也就是说,两个函数的指针形式是一样的,因此定义重复,我们把原代码再次改一下,把printTest的声明改成这种形式:void printTest(const int &data, int num),再次编译、运行,产生的结果如下:

从截图可以清晰的看到,参数3被绑定到了printTest的第一个参数上,而第二个参数则会接收容器中的变量,该段代码产生的效果和手写循环一样。bind2nd的用法和bind1st一样,只不过它是把变量绑定到函数第二个参数上,假如用bind2nd(ptr_fun(printTest) , 3)代替上面的代码的话,会输出这样的结果:


从截图上可以看到,3被绑定到了第二个参数上。


讨论完了全局函数的情况,该讨论一下类中成员函数的情况了,其实成员函数和全局函数在转化成functor的原理上都是一样的,都是需要借用一个适配器,这个适配器可以让functor所需要的typrdef生效,而成员函数所需要的适配器则是mem_fun和mem_fun_ref,关于这两个函数的区别在上面已经提到,不做赘述,仅举一例来说明这个函数的用法。


在上个程序中,printTest是一个全局函数,可以被直接调用,现在,我们把它封装到一个类中,经过改造后的代码如下:

#include <iostream>    #include <vector>    #include <iostream>    #include <algorithm>      using namespace std;      class print  {  private:      int data;    public:      print(){}      print(int _data)      {          data = _data;      }      void printTest(int num)        {            int result = data+num;          cout <<"data: "<<data<<" num: "<<num<<" result: "<<result << endl;        }    };  int main()    {        print p1(1);      print p2(2);      print p3(3);        vector<print> v;      v.reserve(3);      v.push_back(p1);      v.push_back(p2);      v.push_back(p3);        for_each(v.begin(),v.end(),bind2nd(mem_fun_ref<void,print,int>(&print::printTest),3));          return 0;    }    

 pla

那个for_each语句中,mem_fun_ref后面一连串的模板参数让人看起来有点晕,但是一点都不复杂,第一个参数是函数对象的返回类型,在这个程序里,printTest返回的是个void,第二个是调用函数的类,即是print类,第三个是绑定的参数类型,在printTest中就是int。之所以显式声明出模板参数,是为了演示需要,如果你愿意,你完全可以去掉哪些参数,而直接使用mem_fun_ref(&print::printTest),这是完全没有问题的,模板函数不必一直都显式指定参数类型。还有一点需要注意的就是,对于成员函数的变量绑定,肯定是用bind2nd绑定器的,因为默认状态下,第一个变量已经绑定给了调用函数的对象上,因此只有一个变量可以绑定,而mem_fun_ref也明确规定了转化的目标函数只能无参数或者具有一个参数。


函数对象和绑定器是密切相关的,本文对绑定器原理方面并没有做过多的叙述,如果各位有兴趣,可以自行搜索一下binder,以加深对函数对象的理解。

原创粉丝点击