STL仿函数

        仿函数functors另名函数对象functions objects。在之前的算法介绍中,我们已经用到了仿函数。此章,我们对仿函数做一个更详细的介绍。

仿函数概览

        这一节所探索的东西,在STL历史上有两个不同的名称。仿函数(functors)是早期的命名,C++标准规格定案后所采用的新名称是函数对象(function objects)。

        就实现意义而言,“函数对象”比较贴切:一种具有函数特质的对象。不过就其行为而言,以及就中文用词的清晰漂亮与独特性而言,“仿函数”一词比较突出。因此,本书绝大部分时候采用“仿函数”一词。这种东西在调用者可以像函数一样被调用,在被调用者则以对象所定义的function call operator扮演函数的实质角色。

        仿函数的作用主要是什么?从STL所提供的各种算法,往往存在两个版本,其一版本使用了默认操作符,另一个版本可以传入仿函数。例如sort,默认的比较操作符为operator<,另一个版本允许用户指定任何操作,务求排序后的两两相邻元素都能令操作结果为true。亦即,要将某种操作当做算法的参数,唯一办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数;或者将该操作设计为一个所谓的仿函数(事实上为class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。

        根据以上陈述,既然函数指针可以达到“将数组操作当做算法的参数”,那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求----函数指针无法和STL其它组件搭配,产生更灵活的变化。

        就实现观点而言,仿函数其实就是一个“行为类似函数”的对象。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写、重载)function call运算子(operator())。拥有这样的算子后,我们就可以再仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator(),示例如下:

#include <functional>
#include <iostream>
using namespace std;

int main() {
    greater<int> ig;
    cout << boolalpha << ig(4, 6) << endl;  // false
    cout << greater<int>()(6, 4) << endl;   // true
    return 0;
}

        其中第一种用法比较为大家所熟悉,greate<int> ig的意思是产生一个名为ig的对象,ig(4,6)则是调用其operator(),并给予两个参数4,6。第二种用法中的greater<int>()意思是产生一个临时(无名的)对象,之后的(6,4)才是指定的两个参数6,4.

        上述第二种语法在一般情况下不常见,但是对仿函数而言,确实主流用法。

        STL仿函数的分类,若以操作数(operand)的个数划分,可分为一元和二元仿函数,若以功能划分,可分为算术运算,关系运算,逻辑运算三大类。任何应用程序欲使用STL内建的仿函数,必须包含(include)头文件<functional>,SGI将它们实际定义于<stl_function.h>中。

可适配(Adaptable)的关键

        在STL六大组件中,仿函数可以说是体积最小,观念最简单、实现最容易的一个。但是小兵也能立大功----它扮演一种“策略”角色,可以让STL算法有更灵活的演出。而更加灵活的关键,在于STL仿函数的可适配性(adaptablility)。

        是的,STL仿函数应该有能力被函数适配器(function adapter,下一章节会介绍)修饰,彼此像积木一样地串接。为了拥有适配能力,每一个仿函数必须定义自己的响应型别(associative types),就像迭代器如果要融入整个STL大家庭,也必须依照规定定义自己的5个相应型别一样。这些相应型别是为了让适配器能够取出,获得仿函数的某些信息。相应型别都只是一些typedef,所有必要操作在编译期就全部完成了,对程序的执行效率没有任何影响,不带来任何额外负担。

        仿函数的响应型别主要用来表现函数参数型别和传回值型别。为了方便起见,<stl_function.h>定义了两个classes,分别代表一元仿函数和二元仿函数(STL不支持三元仿函数),其中没有任何data members或member functions,唯有一些型别定义。任何仿函数,只需依个人需求选择继承其中一个class,便自动拥有了那些相应型别,也就自动拥有了适配能力。

unary_function

        unary_function用来呈现一元函数的参数型别和返回值型别。其定义非常简答:

// STL规定,每一个Adaptable Unary Function 都应该继承此型别
template<class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;
    typedef Result result_type;
};

        一旦某个仿函数继承了unary_function,其用户便可以取得该仿函数的参数型别,并以相同手法取得其返回值型别:

// 以下仿函数继承了unary_function
template <class T>
struct negate : public unary_function<T, T> {
    T operator() (const T &x) const { return -x;};
};

// 以下适配器(adapter)用来表示某个仿函数的逻辑负值
template <class Predicate>
class unary_negate {
    ...
public:
    bool operator() (const typename Predicate::argument_type&x) const {
        ...
    }
};

binary_function

        binary_function用来呈现二元函数的第一参数型别、第二参数型别,以及返回值型别。其定义非常简单:

// STL规定,每一个Adaptable 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;
};

        一旦某个仿函数继承了binary_function,其用户便可以这样取得该仿函数的各种相应型别:

// 以下仿函数继承了binary_function
template <class T>
struct plus : public binary_function<T, T, T> {
    T operator() (const T& x, const T& y) const { return x + y;}
};

算术类(Arithmetic)仿函数

        STL内建的“算术类仿函数”,支持加法、减法、乘法、除法、取模(余数)、和取负(negate)运算,除了negate运算为一元运算,其它都是二元运算

  • 加法:plus<T>
  • 减法:munus<T>
  • 乘法:multiplies<T>
  • 除法:divides<T>
  • 取模(modulus):modulus<T>
  • 取负(negate):negate<T>

以下为上述6个运算的仿函数定义

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

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

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

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

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

template <class T>
struct negate : public unary_function<T, T> {
    T operator() (const T& x) const { return -x; }
};

        这些仿函数所产生的对象,用法和一般函数完全相同。当然,我们也可以产生临时对象来履行函数功能。下面是实例,显示两种用法:

#include <functional>
#include <iostream>
using namespace std;

int main() {
    plus<int> plusobj;
    minus<int> minusobj;
    multiplies<int> multiobj;
    divides<int> dividesobj;
    modulus<int> modulusobj;
    negate<int> negateobj;

    cout << plusobj(8, 3) << endl;   // 11
    cout << minusobj(8, 3) << endl;  // 5
    cout << multiobj(8, 3) << endl;  // 24
    cout << dividesobj(8, 3) << endl;  // 2
    cout << modulusobj(8, 3) << endl;  // 2
    cout << negateobj(8) << endl;  // -8

    cout << plus<int>()(8, 3) << endl;   // 11
    cout << minus<int>()(8, 3) << endl;  // 5
    cout << multiplies<int>()(8, 3) << endl;  // 24
    cout << divides<int>()(8, 3) << endl;  // 2
    cout << modulus<int>()(8, 3) << endl;  // 2
    cout << negate<int>()(8) << endl;  // -8
    return 0;
}

关系运算类(Relational)仿函数

        STL内建的“关系运算类仿函数”支持了等于、不等于、大于、大于等于、小于、小于等于等六种运算。每一个都是二元运算

  • 等于(equality):equal_to<T>
  • 不等于(inequality):not_equal_to<T>
  • 大于(greater than):greater<T>
  • 大于或等于(greater than or equal): greate_equal<T>
  • 小于(less than):less<T>
  • 小于或等于(less than or equal):less_equal<T>

以下为6个关系运算类仿函数的定义:

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

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

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; }
};

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

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

这些仿函数的功能一目了然,有兴趣的读者可以进行测试验证。

逻辑运算类(Logical)仿函数

        STL内建的仿函数即我们所熟知的与、或、非运算符;其代码定义如下:

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

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

template <class T>
struct logical_not : public unary_function<T, bool> {
    bool operator() (const T& x) const { return !x; }
};

注意:其中的类型T,并不是bool,亦即对于任意类型如果能隐式的转换为bool类型,就可以直接使用上述三种仿函数。同时如果class T,定义了操作符&&,||,!,当然也可以调用上述三种仿函数。相对于重新定义三个操作符,显然通过隐式将类型为T的对象隐式转换为bool,来得更便捷。

证同(identity)、选择(select)、投射(project)

        这一节介绍的仿函数,都只是将其参数原封不动的传回。其中某些仿函数对传回的参数有可以的选择,或是可以的忽略。之所以不在STL或其它泛型程序设计过程中直接使用原本极其简单的identity、project、select等操作,而要再划分一层出来,全是为了间接性----间接性是抽象化的重要工具。

        C++标准规格并未涵盖本节所列的任何一个仿函数,不过他们常常存在于各个实现品中作为内部运用。以下是SGI STL的版本。

// 证同函数(identity function)。任何熟知通过此函数后,不会有任何改变
// 此式运用于<stl_set.h>中,用来指定RB-tree所需的KeyOfValue op
// 那是因为set元素的键值即实值,所以采用identity
template <class T>
struct identity : public unary_function<T, T> {
    const T& operator() (const T& x) const { return x; }
};

//选择函数(selection function):接受一个pair, 传回其第一个元素
//此式运用于<stl_map.h>,用来指定RB-tree所需的KeyOfValue op
//由于map系以pair元素的第一元素为其键值,所以采用select1st
template<class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
    const typename Pair::first_type& operator() (const Pair& x) const {
        return x.first;
    }
};

//选择函数(selection function):接受一个pair, 传回其第二个元素
template<class Pair>
struct select2nd : public unary_function<Pair, typename Pair::first_type> {
    const typename Pair::first_type& operator() (const Pair& x) const {
        return x.second;
    }
};

// 投射函数:传回第一个参数,忽略第二个参数
template <class Arg1, class Arg2>
struct project1st : public binary_function<Arg1, Arg2, Arg1> {
    Arg1 operator() (const Arg1& x, const Arg2&) const { return x; }
};

// 投射函数:传回第二个参数,忽略第一个参数
template <class Arg1, class Arg2>
struct project1st : public binary_function<Arg1, Arg2, Arg2> {
    Arg2 operator() (const Arg1& , const Arg2& y) const { return y; }
};

参考文档《STL源码剖析》---侯捷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大明__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值