历史上有两个不同的名称,仿函数(functors)是早期的命名,C++标准规格定案后所采用的新名称是函数对象(function objects)。这种东西在调用者可以像函数一样地被调用(调用),在被调用者则以对象所定义的function call operator扮演函数的实质角色。
STL仿函数的分类,若以操作数(operand) 的个数划分,可分为一元和二元仿函数;若以功能划分,可分为算术运算(Arithmetic) 、关系运算(Rational)逻輯运算(Logical) 三大类。任何应用程序欲使用STL内建的仿函数,都必须含入
函数指针可以达到“ 将整组操作当做算法的参数”,那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求---函数指针无法和STL其它组件搭配,产生更灵活的变化。
就实现观点而言,仿函数其实就是个“行为类似函数”的对象。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写、重载) function call 运算子(operator()) .拥有这样的运算子后,就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator(),像这样:
greater
上述第二种语法对仿函数而言是主流用法。
STL仿函数应该有能力被函数配接器(function adapter)修饰,彼此像积木一样地串接。为了拥有配接能力,每一个仿函数必须定义自己的相应型别( associative types),就像迭代器如果要融人整个STL大家庭,也必须依照规定定义自己的5个相应型别一样。这些相应型别是为了让配接器能够取出,获得仿函数的某些信息。相应型别都只是一些typedef,所有必要操作在编译期就全部完成了,对程序的执行效率没有任何影响,不带来任何额外负担。
仿函数的相应型别主要用来表现函数参数型别和传回值型别。为了方便起见,
unary_ function用来呈现一元函数的参数型别和回返值型别。binary_function用来呈现二元函数的第一参数型别、第二参数型别,以及回返值型别。
配接器配接器(adapters) 在STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。Adapter 这个概念事实上是一种设计模式(design pttemn) ,《Design Patterns》一书提到23个最普及的设计模式,其中对adapter样式的定义如下:将一个class 的接口转换为另一个class 的接口,使原本因接口不兼容而不能合作的classes可以一起运作。
STL所提供的各种配接器中,改变仿函数(functors)接口者称为function adapter,改变容器(containers) 接口者称为container adapter,改变迭代器(iterators)接口者称为iterator adapter。
container adaptersSTL提供的两个容器queue 和stack, 其实都只不过是-种配接器。它们修饰deque 的接口而成就出另一种容器风貌。stack的底层由deque构成.从以下接口可清楚看出stack与deque的关系:
template> class stack { protected: Sequence c; // 底层容器 };
C++ Standard规定客户端必须能够从
queue与之类似。
iterator adaptersSTL提供了许多应用于迭代器上的配接器,包括insert iterators, reverse iterators, iostream iterators. C++ Standard规定它们的接口可以藉由
所谓isert iterators, 可以将一般迭代器的赋值(assign) 操作转变为插入(insert)操作。这样的迭代器包括专司尾端插人操作的back_insert_iterator,专司头端插入操作的front_insert_iterator,以及可从任意位置执行插入操作的insert_iterator。由于这三个iterator adapters的使用接口不是十分直观,因此STL提供三个相应函数: back inserter() 、front_ inserter()、inserter(), 如图所示,提升使用时的便利性。
reverse iterators可以将一般迭代器的行进方向逆转,使原本应该前进的operator++变成了后退操作,使原本应该后退的operator--变成了前进操作。这种倒转筋脉的性质运用在“从尾端开始进行”的算法上,有很大的方便性。
IOStream Iteratorsistrearm terators可以将迭代器绑定到某个iosteam 对象身上。绑定到istream对象(例如std::cin)身上的,称为istream_iterator拥有输入功能;绑定到ostream对象(例如std::cout)身上的,称为ostream iterator拥有输出功能。这种迭代器运用于屏幕输出非常方便。以它为蓝图稍加修改,便可适用于任何输出或输人装置上。例如可以在透彻了解iostream lterators 的技术后,完成一个绑定到Internet Explorer cache 身上的迭代器,或是完成一个系结到磁盘目录上的迭代器。
不像稍后即将出场的仿函数配接器(functoradapters)总以仿函数作为参数,这里所介绍的迭代器配接器(iterator adapters) 很少以迭代器为直接参数(通常它们以容器为直接参数,而每个容器都有自己专属的迭代器,因此这里所谈的配接器事实上是以容器的迭代器为间接参数。当然C++ 语法并无所谓间接参数)。所谓对迭代器的修饰,只是一种观念上的改变(赋值操作变成插入操作、前进变成后退、绑定到特殊装置上等) 。
functor adaptersfunctor adapters (亦称为function adapters)是所有配接器中数量最庞大的,其配接灵活度也是前二者所不能及,可以配接、配接、再配接。这些配接操作包括系结(bind) 、否定(negate) ,组合(compose) 以及对一般函数或成员函数的修饰(使其成为一个仿函数) . C++ Standard规定这些配接器的接口可由
not1(bind2nd(less
这个式子将less
再举一个例子,假设希望对序列中的每一个元素都做某个特殊运算,这个运算的数学表达式为:
f(g(elem)),其中f和g都是数学函数,那么可以这么写:
compose1(f(x),g(y));
例如希望将容器内的每一个元索v进行(v+2)*3 的操作,可以令
f(x)= x*3, g(y)= y+2,并写下这样的式子:
compose1(bind2nd(multiplies
//第一个参数当做f(),第二个参数当做g()
这一长串形成一个表达式,可以拿来和任何接受表达式的算法搭配。不过这个算式会改变参数的值,所以不能和non mutating算法搭配。例如不能和for_each搭配,但可以和transform搭配将结果输往另一地点。
由于仿函数就是“将function call操作符重载”的一种class,而任何算法接受一个仿函数时,总是在其演算过程中调用该仿函数的operator(),这使得不具备仿函数之形、却有真函数之实的“一般函数"和“成员函数(member functions)"感到为难。为此STL又提供了为数众多的配接器,使“一般函数”和“成员函数”得以无缝隙地与其它配接器或算法结合起来。当然,STL所提供的这些配接器不可能完全满足你的所有需求,例如它没有能够提供我们写出“大于5且小于10”或是“大于8或小于6” 这样的算式。不过对STL源代码有了一番彻底研究后,要打造专用的配接器不是难事。
请注意,所有期望获得配接能力的组件本身都必须是可配接的(adapable)。换句话说一元仿函数必须继承自unary_function,二元仿函数必须继承自binary_function,成员函数必须以mem_fun 处理过,一般函数必须以ptr_fun处理过。一个未经ptr_fun处理过的一般函数虽然也可以函数指针(pointer to function)的形式传给STL算法使用,却无法拥有任何配接能力。



