码迷,mamicode.com
首页 > 其他好文 > 详细

仿函数(functors)

时间:2014-07-27 11:56:33      阅读:227      评论:0      收藏:0      [点我收藏+]

标签:

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

什么是仿函数呢? 

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

看一个例子:

bubuko.com,布布扣

上例中, 我们定义了一个类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。

bubuko.com,布布扣


注意区分, 对于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:

bubuko.com,布布扣

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.



bubuko.com,布布扣


 

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

bubuko.com,布布扣


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

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

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

bubuko.com,布布扣

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

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

4

5

6

7

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

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

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

bubuko.com,布布扣

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

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


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

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

bubuko.com,布布扣

 

使用模板更加的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是变量:

bubuko.com,布布扣


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

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

bubuko.com,布布扣


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

bubuko.com,布布扣


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


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


bubuko.com,布布扣


上例中, 我们定义了一个 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显示的另一个解法:

bubuko.com,布布扣


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



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

bubuko.com,布布扣

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

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


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

bubuko.com,布布扣


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

我们利用bind 构造一个functor。 

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

bubuko.com,布布扣


上述代码号好读多了。

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


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

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

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

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


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


bubuko.com,布布扣

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

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


bubuko.com,布布扣


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

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

bubuko.com,布布扣

 现在可以通过编译了。


从上面的例子中, 我们不难看出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。 

bubuko.com,布布扣



仿函数(functors)

标签:

原文地址:http://blog.csdn.net/a130737/article/details/38147143

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!