STL之函数对象

    STL中有很多算法函数(包含在algorithm)都需要提供一个称为函数对象(Function Object)类型的参数。为什么需要这样一个参数?

    首先这些算法通常是对容器(即存放一堆同类元素的对象,比如list、vector)内的诸多元素进行某种操作,比如sort是对该容器内的这些元素进行排序、find_if是查找该容器内符合某种条件的元素、count_if是计算该容器内符合某种条件的元素总数。

    为了算法可复用,其中的子操作应该是参数化的,比如sort的排序原则(顺序、逆序)、find_if/count_if的匹配条件。函数对象就是用来描述这些子操作的。

例S-1
//////////////////////////////////////////////////////////////////////////
 1 #include <iostream>
 2 #include <iterator>
 3 #include <algorithm>
 4 #include <functional>
 5
 6 using namespace std;
 7 void Add(int & ri)
 8 {
 9     ri += 5;
10 }
11 struct Sub : public unary_function<int,void>
12 {
13     void operator() (int & ri)
14     {
15         ri -= 5;
16     }
17 };
18
19 int main()
20 {
21     int a[10]={0,1,2,3,4,5,6,7,8,9};
22     copy(a,a+10,ostream_iterator<int>(cout," "));
23     cout << endl;
24     for_each(a,a+10,Add);
25     copy(a,a+10,ostream_iterator<int>(cout," "));
26     cout << endl;
27     for_each(a,a+10,Sub());
28     copy(a,a+10,ostream_iterator<int>(cout," "));
29     cout << endl;
30 }
//////////////////////////////////////////////////////////////////////////

    先看看for_each函数的原型:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator first, InputIterator last, UnaryFunction f);

    前两个参数分别说起始元素和终止元素的地址,第三个参数是一个一元函数。

    例S-1中,Add和Sub()都是函数对象,其中Add就是一个全局函数,在24行把Add作为for_each的参数。

    在27行,用Sub结构类型实例化一个函数对象传给for_each作参数。Sub从基类(事实上是个结构)unary_function派生,unary_function的定义如下:

template <class _Arg, class _Result>
struct unary_function {
    typedef _Arg argument_type;
    typedef _Result result_type;
};

    函数对象可以是一个普通函数、可以是函数指针,可以跟其对象一样是某个类的一个实例,但该类必须定义圆括号“()”运算符。

    函数对象描述的是一种操作,它也可以有参数,根据函数对象的参数个数,STL算法用到的主要有三类:

  1. 没有参数的函数对象(Generator),相当于“f()”,比如:
    vector<int> V(10);
    generate(V.begin(), V.end(), rand);
    其中的“rand”就是属于Generator。
  2. 一个参数的函数对象、一元函数(Unary Function),相当于“f(x)”,比如上面的“Add”,“Sub()”也返回一个一元函数对象。STL中用unary_function定义了一元函数基类。
  3. 两个参数的函数对象、二元函数(Binary Function),相当于“f(x,y)”,比如:
    struct less_mag : public binary_function<double, double, bool>
    {
        bool operator()(double x, double y) { return fabs(x) < fabs(y); }
    };
    binary_function定义了二元函数基类:
    template <class _Arg1, class _Arg2, class _Result>
    struct binary_function {
        typedef _Arg1 first_argument_type;
        typedef _Arg2 second_argument_type;
        typedef _Result result_type;
    };

    函数对象也有返回值,返回值是bool型的函数对象称谓词(Predicate),通常称返回bool的一元函数为谓词,返回bool的二元函数为二元谓词。

    有时候,需要把一个二元函数对象转换为一元函数对象、或者组合几个二元谓词为一个谓词。

例S-2
//////////////////////////////////////////////////////////////////////////
 1 #include <iostream>
 2 #include <iterator>
 3 #include <algorithm>
 4 #include <functional>
 5
 6 using namespace std;
 7 int Mul(int ri,int multiplier)
 8 {
 9     ri *= multiplier;
10     return ri;
11 }
12
13 int main()
14 {
15     int a[10]={0,1,2,3,4,5,6,7,8,9};
16     int b[10];
17     copy(b,b+10,ostream_iterator<int>(cout," "));
18     cout << endl;
19     transform(a,a+10,b,bind2nd(ptr_fun(Mul),5));
20     copy(b,b+10,ostream_iterator<int>(cout," "));
21     cout << endl;
22 }
//////////////////////////////////////////////////////////////////////////

    例S-2中,19行调用ptr_fun把一个普通全局函数转换成一个二元函数对象,并调用bind2nd函数把该二元函数对象的第二个参数multiplier绑定为5成为一个一元函数对象。

    例S-2就是把a[i]*5存入b[i]中,可以使用:

transform(a,a+10,b,bind2nd(ptr_fun(Mul),7));

    把a[i]*7存入b[i]中。

    ptr_fun有两个原型:
template <class Arg, class Result>
pointer_to_unary_function<Arg, Result>
ptr_fun(Result (*x)(Arg));

template <class Arg1, class Arg2, class Result>
pointer_to_binary_function<Arg1, Arg2, Result>
ptr_fun(Result (*x)(Arg1, Arg2));

    这里显然用到第二个原型:
template <class _Arg1, class _Arg2, class _Result>
inline pointer_to_binary_function<_Arg1,_Arg2,_Result>
ptr_fun(_Result (*__x)(_Arg1, _Arg2)) {
    return pointer_to_binary_function<_Arg1,_Arg2,_Result>(__x);
}

    所以上面的语句完全可以写成:
transform(a,a+10,b,bind2nd(pointer_to_binary_function<int,int,int>(Mul),7));

    “pointer_to_binary_function<_Arg1,_Arg2,_Result>”是个模板类,它的构造函数以一个二元函数指针为参数:
template <class _Arg1, class _Arg2, class _Result>
class pointer_to_binary_function : public binary_function<_Arg1,_Arg2,_Result> {
protected:
    _Result (*_M_ptr)(_Arg1, _Arg2);
public:
    pointer_to_binary_function() {}
    explicit pointer_to_binary_function(_Result (*__x)(_Arg1, _Arg2)) : _M_ptr(__x) {}
    _Result operator()(_Arg1 __x, _Arg2 __y) const {
        return _M_ptr(__x, __y);
    }
};

    它从binary_function派生,有个保护成员变量_M_ptr保存二元函数指针,定义了“()”运算符,其中调用_M_ptr指向的二元函数。

    bind2nd也是个内联函数:
template <class _Operation, class _Tp>
inline binder2nd<_Operation>
bind2nd(const _Operation& __fn, const _Tp& __x)
{
    typedef typename _Operation::second_argument_type _Arg2_type;
    return binder2nd<_Operation>(__fn, _Arg2_type(__x));
}

    所以刚才的语句又可以写成:

transform(a,a+10,b,binder2nd<pointer_to_binary_function<int,int,int> >(pointer_to_binary_function<int,int,int>(Mul),5));

    “binder2nd<_Operation>”也是一个模板类,“_Operation”在这里被“pointer_to_binary_function<int,int,int>”替代,其构造函数有两个参数,第一个是_Operation类对象,第二个是_Operation的第二个参数类型常量:
template <class _Operation>
class binder2nd : public unary_function<typename _Operation::first_argument_type, typename _Operation::result_type> {
protected:
    _Operation op;
    typename _Operation::second_argument_type value;
public:
    binder2nd(const _Operation& __x, const typename _Operation::second_argument_type& __y) : op(__x), value(__y) {}
    typename _Operation::result_type operator()(const typename _Operation::first_argument_type& __x) const {
        return op(__x, value);
    }
};

    它从unary_function派生,也有个保护成员变量op保存实例化时传入的_Operation对象,并有保护成员变量value保存被绑定的第二个参数值,也定义了“()”运算符,其中调用二元函数对象op的“()”运算符。

    从“transform(a,a+10,b,bind2nd(ptr_fun(Mul),5));”到“transform(a,a+10,b,binder2nd<pointer_to_binary_function<int,int,int> >(pointer_to_binary_function<int,int,int>(Mul),5));”,虽然从语法上复杂了(事实上也没有人会这样写C++代码),但却弄清了函数对象的来龙去脉。

    它充分利用了C++的模板特性,当然也依靠了其它一些关键字,如typedef、typename。

    象pointer_to_binary_function、binder2nd这样的模板类比较特殊,它们对其它函数对象进行修改或组装生成一个新的函数对象。比如pointer_to_binary_function它把普通全局函数Mul改成一个二元函数对象,binder2nd把pointer_to_binary_function实例化出来的二元函数对象和一个常量5组装生成一个一元函数对象。这样的模板类在STL中被称为函数对象适配器(function object adaptors)。

 STL中包含许多预定义的函数对象,其中有:
  1. 算术操作:plus, minus, multiplies, divides, modulus, 和 negate,例:

    assert(V2.size() >= V1.size() && V3.size() >= V1.size());
    transform(V1.begin(), V1.end(), V2.begin(), V3.begin(),modulus<int>());

    把V1[i]%V2[i]存入V3[i]。

     

  2. 比较操作:equal_to, not_equal_to, greater, less, greater_equal, 和 less_equal,例:

    list<int>::iterator first_nonnegative = find_if(L.begin(), L.end(), bind2nd(greater_equal<int>(), 0));

    在L中找到第一个大于等于0的元素。

     

  3. 逻辑操作:logical_and, logical_or, 和 logical_not,例:

    char str[MAXLEN];
    const char* wptr = find_if(str, str + MAXLEN,compose2(logical_or<bool>(),bind2nd(equal_to<char>(), ' '),bind2nd(equal_to<char>(), '/n')));

    在str中找到第一个空格或行尾。

    以上三类都是模板类,要生成一个函数对象需要实例化。同时在使用时,需要注意:

    • 算法需要的参数是什么类型的函数对象,是一元函数还是二元函数。
    • 输入容器内元素类型是否与函数对象参数兼容,输出容器内元素是否与函数对象返回值兼容。

     

  4. 修改、组装已有函数对象生成新的函数对象:unary_compose, binary_compose, unary_negate, binary_negate, binder1st, binder2nd, pointer_to_unary_function, pointer_to_binary_function, mem_fun_t, mem_fun_ref_t, mem_fun1_t 和mem_fun1_ref_t。这类函数对象就是所谓的函数对象适配器,由于书写生涩,一般不会直接实例化,都有对应的辅助函数(一般都是内联型)简化调用。
函数对象模板类名辅助函数名说明
unary_compose compose1 unary_compose(const AdaptableUnaryFunction1& f,
              const AdaptableUnaryFunction2& g);

生成一个一元函数对象,操作为“f(g(x))”;vc6/7自带stl没有定义。

binary_compose compose2 binary_compose(const AdaptableBinaryFunction& f,
               const AdaptableUnaryFunction1& g1,
               const AdaptableUnaryFunction1& g2);

生成一个一元函数对象,操作为“f(g1(x), g2(x))”;vc6/7自带stl没有定义。

unary_negate not1 unary_negate(const AdaptablePredicate& pred);

一元谓词取非。

binary_negate not2 binary_negate(const AdaptableBinaryPredicate& pred);

二元谓词取非。

binder1stbind1st binder1st(const AdaptableBinaryFunction& F,
          AdaptableBinaryFunction::first_argument_type c);

把二元函数对象的第一个参数绑定为c,生成一个一元函数对象。

binder2nd  bind2nd binder2nd(const AdaptableBinaryFunction& F,
          AdaptableBinaryFunction::second_argument_type c);

把二元函数对象的第二个参数绑定为c,生成一个一元函数对象。

pointer_to_unary_function ptr_fun 把普通全局函数或全局函数指针(只能有1个或2个参数)转换成函数对象。

ptr_fun有两个原型分别生成pointer_to_unary_function对象和pointer_to_binary_function对象。

pointer_to_binary_function
mem_fun_tmem_fun把某个类的成员函数(只能有0个或1个参数,因为类成员函数的第一个参数已定义为this指针)转换成函数对象。

mem_fun有两个原型分别生成mem_fun_t对象(是个一元函数对象)和mem_fun1_t对象(是个二元函数对象)。

mem_fun1_t
mem_fun_ref_tmem_fun_ref类似mem_fun,区别如下:

struct T{
     print(){}
};

list<T*> lstT;
for_each(lstT.begin(),lstT.end(),mem_fun(&T::print));

list<T> lstT;
for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));

mem_fun1_ref_t
   

例S-3

//////////////////////////////////////////////////////////////////////////
#include <string>
#include <list>
#include <algorithm>
#include <iostream>
using namespace std;
class T
{
public:
    T(int i1,int i2,string s){score1 = i1;score2 = i2;name = s;totalscore = i1+i2;}
    int score1;
    int score2;
    int totalscore;
    string name;
    void print()
    {
    cout << name << "/t/t" << score1 << "/t/t" << score2 << "/t/t" << totalscore << endl;
    }
};
int main(int argc,char ** argv)
{
    list<T> lstT;
    lstT.push_back(T(34,75,"TOM"));
    lstT.push_back(T(23,84,"JIM"));
    lstT.push_back(T(43,63,"JOHN"));
    lstT.push_back(T(61,69,"MIKE"));
    lstT.push_back(T(21,90,"ROBIN"));
    lstT.push_back(T(59,87,"SALLY"));
    lstT.push_back(T(54,72,"LINDA"));
    lstT.push_back(T(43,79,"JAMES"));
    for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));
    cout << endl;

    list<T*> lstTa;
    lstTa.push_back(new T(34,75,"tom"));//"guw" in vi can chanage a word lowercase.
    lstTa.push_back(new T(23,84,"jim"));
    lstTa.push_back(new T(43,63,"john"));
    lstTa.push_back(new T(61,69,"mike"));
    lstTa.push_back(new T(21,90,"robin"));
    lstTa.push_back(new T(59,87,"sally"));
    lstTa.push_back(new T(54,72,"linda"));
    lstTa.push_back(new T(43,79,"james"));
    for_each(lstTa.begin(),lstTa.end(),mem_fun(&T::print));
    cout << endl;
}
//////////////////////////////////////////////////////////////////////////

 

   阅读STL源码是提升C++经验值的有效途径,虽然其书写风格让不少人觉得别扭,但认真阅读也会让人受益匪浅。下面是我从STL源码中吸取精华、自己实现的一个函数对象适配器binder3rd。

    STL提供了把一个二元函数对象转换成一元函数对象的函数(bind1st/bind2nd),但我有时需要把一个三元函数对象转换成二元函数对象,STL没有提供这样的函数对象适配器。

    先定义一个类型描述三元函数:

template <class _Arg1, class _Arg2, class _Arg3, class _Result>
struct ternary_function {
    typedef _Arg1 first_argument_type;
    typedef _Arg2 second_argument_type;
    typedef _Arg3 third_argument_type;
    typedef _Result result_type;
};

    某个类的二元成员函数应该是一个三元函数,所以二元成员函数应该从ternary_function派生。

template <class _Ret, class _Tp, class _Arg1, class _Arg2>
class const_mem_fun2_ref_t : public ternary_function<_Tp,_Arg1,_Arg2,_Ret> {
public:
    explicit const_mem_fun2_ref_t(_Ret (_Tp::*__pf)(_Arg1,_Arg2) const) : _M_f(__pf) {}
    _Ret operator()(const _Tp& __r, _Arg1 __x, _Arg2 __y) const { return (__r.*_M_f)(__x,__y); }
private:
    _Ret (_Tp::*_M_f)(_Arg1,_Arg2) const;
};

    binder3rd的目的是把一个三元函数对象转换成二元函数对象,所以binder3rd要从binary_function派生。

template <class _Operation>
class binder3rd : public binary_function<typename _Operation::first_argument_type,typename _Operation::second_argument_type,typename _Operation::result_type> {
protected:
    _Operation op;
    typename _Operation::third_argument_type value;
public:
    binder3rd(const _Operation& __x,const typename _Operation::third_argument_type& __y)
: op(__x), value(__y) {}
    typename _Operation::result_type operator()(const typename _Operation::first_argument_type& __x,const typename _Operation::second_argument_type& __y) const {return op(__x, __y, value); }
};

完整的代码如下:

#include <string>
#include <list>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <cstdio>
#include <cctype>
#include <cmath>
using namespace std;

template <class _Arg1, class _Arg2, class _Arg3, class _Result>
struct ternary_function {
    typedef _Arg1 first_argument_type;
    typedef _Arg2 second_argument_type;
    typedef _Arg3 third_argument_type;
    typedef _Result result_type;
};

template <class _Ret, class _Tp, class _Arg1, class _Arg2>
class const_mem_fun2_ref_t : public ternary_function<_Tp,_Arg1,_Arg2,_Ret> {
public:
    explicit const_mem_fun2_ref_t(_Ret (_Tp::*__pf)(_Arg1,_Arg2) const) : _M_f(__pf) {}
    _Ret operator()(const _Tp& __r, _Arg1 __x, _Arg2 __y) const { return (__r.*_M_f)(__x,__y); }
private:
    _Ret (_Tp::*_M_f)(_Arg1,_Arg2) const;
};

template <class _Operation>
class binder3rd : public binary_function<typename _Operation::first_argument_type,typename _Operation::second_argument_type,
typename _Operation::result_type> {
protected:
    _Operation op;
    typename _Operation::third_argument_type value;
public:
    binder3rd(const _Operation& __x,const typename _Operation::third_argument_type& __y)
: op(__x), value(__y) {}
    typename _Operation::result_type operator()(const typename _Operation::first_argument_type& __x,const typename _Operation::second_argument_type& __y) const { return op(__x, __y, value); }
};

class T
{
public:
    T(int i1,int i2,string s){score1 = i1;score2 = i2;name = s;totalscore = i1+i2;}
    int score1;
    int score2;
    int totalscore;
    string name;
    bool CompareBy(T aT,int type) const
    {
        switch(type)
        {
        case 0://score1
            return (score1 > aT.score1);
            break;
        case 1://score2
            return (score2 > aT.score2);
            break;
        case 2://totalscore
            return (totalscore > aT.totalscore);
            break;
        default:
            break;
        }
        return false;
    }
    void print()
    {
        cout << name << "/t/t" << score1 << "/t/t" << score2 << "/t/t" << totalscore << endl;
    }
};


int main(int argc,char ** argv)
{
    list<T> lstT;
    lstT.push_back(T(34,75,"TOM"));
    lstT.push_back(T(23,84,"JIM"));
    lstT.push_back(T(43,63,"JOHN"));
    lstT.push_back(T(61,69,"MIKE"));
    lstT.push_back(T(21,90,"ROBIN"));
    lstT.push_back(T(59,87,"SALLY"));
    lstT.push_back(T(54,72,"LINDA"));
    lstT.push_back(T(43,79,"JAMES"));
    for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));
    cout << endl;
    lstT.sort( binder3rd<const_mem_fun2_ref_t<bool,T,T,int> >(const_mem_fun2_ref_t<bool,T,T,int>(&T::CompareBy),0) );
    for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));
    cout << endl;
    lstT.sort( binder3rd<const_mem_fun2_ref_t<bool,T,T,int> >(const_mem_fun2_ref_t<bool,T,T,int>(&T::CompareBy),1) );
    for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));
    cout << endl;
    lstT.sort( binder3rd<const_mem_fun2_ref_t<bool,T,T,int> >(const_mem_fun2_ref_t<bool,T,T,int>(&T::CompareBy),2) );
    for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));
    cout << endl;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值