仿函数

来源:互联网 发布:面部交换软件下载 编辑:程序博客网 时间:2024/04/29 03:30

仿函数,又叫做函数对象,是一个重载了"()"运算符的struct,是STL(标准模板库)六大组件(容器、配置器、迭代器、算法、配接器、仿函数)之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎所有的算法都有仿函数版本。例如,查找算法find_if就是对find算法的扩展,标准的查找是两个元素向等就找到了,但是什么是相等在不同情况下却需要不同的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法本身却不需要改变,这都多亏了仿函数。

看个简单的例子:
struct D {
 D(int i=0){num=i;}
 int num; 
};
struct print_D{
 void operator()(const D* d)const{
      cout<<"I am D. my num="<<d->num<<endl;
    }
};

int main()
{
  vector<D*> V;

  V.push_back(new D(1));
  V.push_back(new D(2));
  V.push_back(new D);
  V.push_back(new D(3));

  for_each(V.begin(), V.end(), print_D());
}
编译输出:

I am D. my num=1
I am D. my num=2
I am D. my num=0
I am D. my num=3

如果使用mem_fun,会方便很多:

struct D {
  D(int i=0){num=i;}
  void print() { cout << "I'm a D. my num=" << num<< endl; }
  int num; 
};

int main()
{
  vector<D*> V;

  V.push_back(new D(1));
  V.push_back(new D(2));
  V.push_back(new D);
  V.push_back(new D(3));

  for_each(V.begin(), V.end(), mem_fun(&D::print));
}

 

mem_fun对于一些多态的虚函数也十分有用

struct B {
 virtual void print() = 0;
};

struct D1 : public B {
 void print() { cout << "I'm a D1" << endl; }
};

struct D2 : public B {
 void print() { cout << "I'm a D2" << endl; }
};

int main()
{
 vector<B*> V;

 V.push_back(new D1);
 V.push_back(new D2);
 V.push_back(new D2);
 V.push_back(new D1);

  for_each(V.begin(), V.end(), mem_fun(&B::print));
}

 

仿函数之所以叫做函数对象,是因为仿函数都是定义了()函数运算操作符的类。例如,STL自带的仿函数equal_to<class Tp>定义为:

template <class _Tp>

struct equal_to : public binary_function<_Tp,_Tp,bool>

{

bool operator()(const_Tp&__x,const_Tp&__y) const { return __x==__y; }

};

在算法内部调用此操作符,如find_if:

template <class_RandomAccessIter,class_Predicate>

_STLP_INLINE_LOOP _RandomAccessIter __find_if(_RandomAccessIter __first, _RandomAccessIter __last,_Predicate __pred,const random_access_iterator_tag &)

{

_STLP_DIFFERENCE_TYPE(_RandomAccessIter) __trip_count = (__last - __first) >> 2;

 

for ( ; __trip_count > 0 ; --__trip_count) {

if (__pred(*__first)) return __first;

++__first;

//以下略

}

 

仿函数的可配接性

仿函数的可配接性是指仿函数能够与其它仿函数配接在一起实现新的功能,如不小于60,可以利用STL自带的not1<int>和less<int>配接而成:not1(bind2nd(less<int>(), 12))。

 

一般而言,通用函数也可以作为仿函数参数传递给算法,但其区别在于“通用函数不具有可配接性”。是否定义成仿函数都具有配接性了呢?也不尽然!只有从unary_function或者binary_funcion继承的仿函数才有配接性。这是为什么呢?

其奥妙在于模板类常见的类型定义,可配接性的关键就在于这些类型定义;如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;

}; 

在STL的适配器中会自动使用到这些类型定义,所以必须声明这些类型。

 

把通用函数转换为仿函数

STL的实现也考虑到会将通用函数作为仿函数来使用,为了保证这些函数的可配接性,即把这些函数转换为仿函数使用,STL也提供了相应的适配器ptr_fun1_base,ptr_fun2_base,其原理也是重载函数调用操作符,在仿函数对象构造时把通用函数作为参数传入,如:

template <class _Arg, class _Result>

class pointer_to_unary_function : public unary_function<_Arg, _Result>

{

protected:

//函数原型

_Result (*_M_ptr)(_Arg);

public:

pointer_to_unary_function() {}

//构造时把函数指针传入

explicit pointer_to_unary_function(_Result (*__x)(_Arg)) : _M_ptr(__x) {}

//()函数运算操作符重载,执行函数功能

_Result operator()(_Arg __x) const { return _M_ptr(__x); }

};

 

把类成员函数转换为仿函数

既然通用函数都能转换为仿函数,带有C++封装性的类的成员函数(当然要是public)也能否转换为仿函数?答案是肯定的,STL也提供了相应适配器。由于返回值和参数的个数不同,这类适配器的数目很多:_Void_mem_fun0_ptr、_Void_mem_fun1_ptr、_Void_const_mem_fun0_ptr、_Void_const_mem_fun1_ptr等。

例子中使用通用函数和成员函数作为仿函数配合STL算法使用。

class Numbers

{

public:

//用于显示

bool display()

{

      cout << *this;

      return true;

}

//用于查找

bool if_equal(int val)

{

     return val == m_val;

}

};

 

如下的语句验证了ptr_fun转换后的仿函数的可配接性:

vector<int>::iterator it = find_if(vNums.begin(), vNums.end(), bind2nd(ptr_fun(if_equal), val));

而for_each(vObjs.begin(), vObjs.end(), mem_fun(&Numbers::display));和vector<Numbers*>::iterator itObj=find_if(vObjs.begin(), vObjs.end(), bind2nd(mem_fun1(&Numbers::if_equal), 3));

说明了如何使用STL的适配器来转换类成员函数。需要说明的是,在转换成员函数时,有引用和指针两个版本,例子程序中使用的是指针版本,所以定义vector时定义元素类型尾Number*。这是因为这时适配器的函数操作符是通过指针形式调用的,如mem_fun1返回mem_fun1_t的内部实现为:

Ret operator()(_Tp* __p, _Arg __x) const { return (__p->*_M_f)(__x); }

 

定义自己的仿函数类型

定义可配接的仿函数,只需要从unary_function和binary_function派生即可,但是STL只定义了这两种类型;但我们有可能需要使用3个参数的仿函数,同时也更能体会可配接性的原理,这里给出了triple_function的函数原型,可以STL的作为一种扩展。

//用于方便提前类的类型定义

#define TRIPLE_ARG(Operation, Type) Operation::Type

//三元函数的类型定义

template<class Arg1, class Arg2, class Arg3, class Result>

struct triple_funcion

{

    //保证可配接性的类型定义

       typedef Arg1 first_argument_type;

       typedef Arg2 second_argument_type;

       typedef Arg3 third_argument_type;

       typedef Result result_type;

};

 

//三元函数的适配器,把第3个参数固定为特定值

template <class Operation>

class binder3rd : public binary_function<typename TRIPLE_ARG(Operation, first_argument_type),

typename TRIPLE_ARG(Operation, second_argument_type), typename TRIPLE_ARG(Operation, result_type)>

{

protected:

       Operation m_op;

       typename Operation::third_argument_type value;

public:

       binder3rd(const Operation& x, const typename Operation::third_argument_type y):m_op(x), value(y){}

    //通过固定第三个参数,把函数转换为binary_function

       typename Operation::result_type operator()(const Operation::first_argument_type& x,

              const Operation::second_argument_type& y) const

       {

              return m_op(x, y, value);

       }

      

};

 

//上层使用的包装类

template<class Operation, class Arg>

inline binder3rd<Operation> bind3rd(const Operation& fn, const Arg& x)

{

       typedef Operation::third_argument_type third_argument_type;

       return binder3rd<Operation>(fn, third_argument_type(x));

}

 

在例子中定义了一个三元仿函数:

class Qualified : public triple_funcion<Student, int, int, bool>

{

public:

       bool operator()(const Student& s, int math, int physics) const

       {

              return s.math > math && s.physics > physics;

       }

};

用于查找数学和物理两科成绩符合条件的学生。

查找时,通过bind3rd和bind2nd把数学和物理的成绩基线定下来:数学>40,物理>60。

it = find_if(it, students.end(), bind2nd(bind3rd(Qualified(), 40), 60));

 

仿函数小巧和作用大,原因是其可配接性和用于算法;可以根据需要把相关函数封装到类中,或者调用基本的函数库来减少开发量。只要知道了STL适配器内部机制,就能定义出符合要求的仿函数来。

0 0