写在前面

STL当中的仿函数

主要内容

函数对象和仿函数

仿函数是早期的命名,采用的新的名称是函数对象。
函数对象就是一种具有函数特质的对象。
这种东西在调用者可以像函数一样被调用,在被调用者则以对象所定义的()操作符扮演函数的实质角色。

仿函数的作用

第一个版本表现在最常用的某种运算
第二个版本表现在最泛化的演算流程,允许用户已模板参数来指定所要采取的策略

实现第二种版本的方式

  • 函数指针
  • 仿函数

函数指针可以达到要求但是为什么要用仿函数呢?因为函数指针不能满足STL对于抽象性的要求,函数指针无法和其他的STL组件搭配。

仿函数本质

仿函数就是行为类似函数的对象。其类别必须重载()操作符。

可配接的关键

仿函数的相应的型别主要用来表现函数的参数和返回值类型,
我们可以提取这些信息相应的型别都只是一种typedef
所有的操作都在编译器完成没有任何的执行效率的损失不带来任何的负担。
STL当中定义了两个类分别代表一元仿函数和2元仿函数,STL不支持3元和多元仿函数。
其中没有任何的数据成员和成员函数,有的只有型别的嵌套定义typedef
任何仿函数只需要依据个人需求继承其中的一个class便自动拥有相应的型别也拥有配接的能力。

unary_function呈现一元函数的参数型别和返回值型别,stl规定每一个可配接的一元函数都应该继承自此类。
binary_function呈现二元函数的参数型别返回值型别。

template <class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;
    typedef Result result_type;
};

template <class Arg1, class Arg2, class Result>
struct binary_function {
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};

因为这两个类都是结构体对外是公开的。这样外界就可以直接使用argument_type,first_argument_type,second_argument_type,result_type这样的类型,对其作出处理。配接器可以通过模板参数获取仿函数的类型,直接就可以通过仿函数类型的作用于获取仿函数的参数类型,返回值类型等等,比如仿函数是二元函数但是我们可以只用其一个参数或者将多个仿函数的参数类型合并到一个函数当中构成一个新的多元函数等等。。就像搭积木一样的拼接可能就是这个配接器的精髓吧。

仿函数的实际用处

不会有人单纯的定义仿函数然后只是像使用普通函数一样使用,大可以直接写函数或者写个lambda表达式即可(该函数只使用一次)。仿函数的主要用途是搭配STL算法使用。
STL的算法都是些函数模板,每个函数一般都会有两个版本,一个版本是使用默认的比较函数(操作函数),另外一个多一个参数的版本是用户可以提供自定义的操作函数的版本。
例如STL的算法accumulate

template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init) {
  for ( ; first != last; ++first)
    init = init + *first;
 return init;
}

template <class InputIterator, class T, class BinaryOperation>
T accumulate(InputIterator first, InputIterator last, T init,
             BinaryOperation binary_op) {
  for ( ; first != last; ++first)
    init = binary_op(init, *first);
 return init;
}

accumulate的第二个版本通过最后一个参数获取函子,这个函子可以是一个函数指针,也可以是一个仿函数对象,也可以是一个lambda表达式。
如果传递的是一个函数指针那么BinaryOperation的类型通过函数模板的参数类型推导机制就可得是一个函数指针类型
如果传递的是一个仿函数对象,那么BinaryOperation的类型就是仿函数的类型。
如果是lambda表达式,支持该技术的编译器会将表达式转换为一个类对象。。就同上了。
所以这里其实也是一种泛型了。

言归正传,binary_op传入怎样的仿函数对象就可以定义init = binary_op(init, *first);当中执行的逻辑
算法和仿函数的结合可以搭配出不同的效果。

一些STL内建的仿函数

算数类仿函数

  • 加法 plus< T>
  • 减法minus< T>
  • 乘法multiplies< T>
  • 除法divides< T>
  • 取模modulus< T>
  • 否定negate< T>

关系运算类仿函数

  • 等于,不等于,大于,小于,大于等于,小于等于。
  • 用的最多的就是大于和小于
template <class T>
struct greater : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x > y; }
};

template <class T>
struct less : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x < y; }
};

STL默认比较函数都是使用的小于号比较也就是less< T >

逻辑运算类仿函数

  • 与或非

证同,选择,投影类仿函数
这个就有点意思了

一般的仿函数都是将其参数原封不动的传回,但是某些仿函数还可以对传回的参数有刻意的选择,或者是刻意的忽略。