转自 http://blog.csdn.net/rendaduiyan/archive/2006/03/09/620303.aspx
仿函数定义
仿函数,又或叫做函数对象,是 STL (标准模板库)六大组件(容器、配置器、迭代器、算法、配接器、仿函数)之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎所有的算法都有仿函数版本。例如,查找算法 find_if 就是对 find 算法的扩展,标准的查找是两个元素向等就找到了,但是什么是相等在不同情况下却需要不同的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法本身却不需要改变,这都多亏了仿函数。
仿函数之所以叫做函数对象,是因为仿函数都是定义了 () 函数运算操作符的类。例如, 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 适配器内部机制,就能定义出符合要求的仿函数来。