仿函数(functors)

来源:互联网 发布:淘宝联盟不提现会怎样 编辑:程序博客网 时间:2024/05/21 22:39

functor(仿函数), 或者称之为function object(函数对象), 是STL的四大组件之一。

什么是仿函数呢? 

一个函数对象是封装在类中, 从而看起来更像是一个对象。 这个类只有一个成员函数, 即重载了() (括号)的运算符。 它没有任何数据。 该类被模板化了, 从而可以应付多种数据类型。

看一个例子:


上例中, 我们定义了一个类X, 然后我们在类中定义了一个运算符, 即括号,(),   该运算符吃一个string类型的参数。

在主程序中, 我们声明了一个类X的对象foo, 然后我们调用仿函数(函数对象)foo ,传进参数“Hi”, 注意我们定义了() 运算符。

 上面中, foo is an instance of X,  but we can use it as if it is a function。

这就是functor的设计思想。 仿函数推广了函数的定义。 也就是说任何表现出函数的特征的都是函数。  例如上例中, X是一个class, 但是表现的像是函数, 我们就称其为函数。 STL中提供了许多预定义的函数对象, 如果要使用, 应包含头文件<functional>。

NOTE: 类型转换函数(type conversion function)和重载() 的函数的区别如下:

下图中的第二个函数数类型转换函数: 将X 转换为一个string。



注意区分, 对于type conversion function, you put type (here string or void) after opertor,   and for defining a functor, you have to put type before the operator(重载括号)。 


BACK to The functor:


Q : 人们为啥要用functor, 而不用普通的函数(Motivation)?

A : 这源于仿函数的优点: 你可以将仿函数想象成为一个smart function, 可以做许多普通函数无法完成的事情。

例如我们可以在上面的类X中定义成员函数。 这样我们可以声明多个不同的functor, 数据不同。 (回忆一下, 对于类对象, 不同类对象的成员函数是共享的, 数据是属于类对象本身的)。

第二点, 一个functor可以由其自己的type。 regular functions can only be differentiate by their signitures。  If 2 functiions have the same signature, then they are the same function.  Howere, 2 functors can be of different type, even if thry have the exact same signature.





 

正是因为仿函数的存在, 才有parametriized function:



上例中, 我们在类X中添加了一个建构子(constructor), 在main 函数中, X(8)(参数化函数, X of 8)调用了普通的参数“Hi”。

Q: 为什么要这样做(上例), 即一个参数是8, 一个参数是“Hi”?

A: 为了说明我们为啥这样做, 下面举一个例子:


显然, 上述中, 我们定义了一个函数, 将输出i 加2

在主函数中, 我们定义了STL,  不难看出, 是输出向量vec 所有的元素都加上2的情况。 所以输出是:

4

5

6

7

然而上述的add2 函数, 从名字上看是加2, 但是当加3 的时候, 如果我们还有add2, 就会出现问题了, 显然not extensible。  

下面说说如何让我们的code 更加的flexible:

一个很明显的做法就是定义一个全局变量(灵活的实现加几 显示):


这样, 我们就可以加上任何val 显示了。

但是, 使用全局变量is a nasty coding practice, and we donot want to do this。


那么我们还有其他的解决办法吗?

是的, 有的, 我们可以定义模板函数, 将val 定义为模板的参数。 如下图, 主函数中, 给出模板的参数。


 

使用模板更加的flexible 了。 我们可以通过改变VAL实现加上任何值在输出的效果。

but, there is still a problem. a template variable is resolved at compile time。 so it has to be a compile time constant。 所以, 下面的程序是错误的, 无法通过编译, 因为x是变量:



那么, 还有别的解决办法吗?

有的, the best solution: 使用仿函数(functor)。



事实上, 我们并不需要要自己去写仿函数。 C++ 的STL中有内置的functor供我们使用。



上述的那些仿函数对于某些算法的实现非常有用。  某些算法使用函数对象进行操作


下面在说说参数绑定(parameter binding)。




上例中, 我们定义了一个 set of integers。 内容为{2, 3, 4, 5}。 

现在我们想要将集合每一个元素乘以10, 然后存储在向量vec 中。 

我们知道有一个functor mulyiplies 可以供我们使用。 

而且我们还有一个transform的算法, 这个算法可以调用multiplies 作用于我们集合的所有元素(如上图), 然后back insert into vec。 这就是我们想要完成的任务。 但是这里存在一个问题。 就是,   functor multiplies 吃两个参数, 然而transform 函数需要的是一个只吃一个参数的functor。 那么我们如何解决这个矛盾呢?

我们可以使用bind function(如上图)。 with the bind function, the placeholders::_1 的意思是functor multiplies 的第一个参数是被替换为an element from myset, 第二个参数是10。 所以最终的结果为如上图最后一行注释。


通过使用bind 函数, 我们给出上面加2显示的另一个解法:



注意, 对于bind 函数只在C++11 引进。 对于更早的版本, 我们使用bindlst, bind2nd.



接下来, 说说如何将一个普通的函数转换成为一个仿函数:


上图中, 我们希望将myset 的每一个元素平方, 然后存储在deque d 中。 我们的第一步是定义一个template class(模板类) function  ,  将函数pow 转换为一个functor f。 

 然后我们使用transform 函数和bind函数将myset 的每一个值二次方, 然后back insert 到deque d 中。 


我们还可以利用函数transfrom 和 bind 函数做些更加fancy 的事情:



例如, 我们想要将上图满足条件的元素拷贝到deque d 中去。 

我们利用bind 构造一个functor。 

然而上述的代码可读性很差。 我们按照如下操作, 编写一个函数:



上述代码号好读多了。

 但是对于C++11而言, 还有更好的解决办法, 那就是使用lambda function, 这样, 我们就不需要额外的编写函数needCopy了:


最后, 我们说functor 在STL中扮演着重要的角色。 

还记得吗, 关联容器总是sorted,  但是排序的标准是什么呢(是从小到大, 还是从大到小?)。

定义关联容器的时候, 例如下例, 模板类set有两个参数, 一个是int(元素类型), 另一个吃一个functor。 默认的比较仿函数(comparison functor)是less。

所以set<int> 等价于 set<<int>, less<less>>, 如下图。


下在假设我们想要按照least significant digit 对集合中的元素进行排序。 (例如主函数中的注释, 1, 2, 3, 5, 7)



接下来, 我们需要定义lsb_less。

假如我们按照如下方式定义:




上述的方式是无法通过编译的。 因为set 需要的是一个function type 作为其第二个参数, 而非一个函数(lsb_less)。

所以, 我们需要定义一个functor 才能解决这个问题。


 现在可以通过编译了。


从上面的例子中, 我们不难看出functor对STL的巨大作用了。 

如果没有functor, 那么associative container will not work。 因为关联容器需要一个function type 作为其第二个参数。


最后, 我们说说, 还有一种特殊的functor or function , 就是predicate。  一个predicate 必须满足两个条件:

(1)返回值必须是bool的类型的。

(2)不能够修改数据。

predicate的一个例子如下: transform 函数将会根据返回值判断是否需要复制。 

predicate 广泛用于STL algorithms, 主要用于比较或者条件判断。  需要记住一点, 就是: you cannot assume predicate is invorked only once per element during the execution of the algorithms.   so it is recommanded that a predicate is a pure function.  which means the return value is purely based on the input value, and has nothing to do with how many times the predicate has been invoked。 




0 0
原创粉丝点击