Posts Tagged 高阶函数
C++高阶函数(一)
0. 前言
看见了标题不要以为以下的内容是C++的高级话题。这里的“高阶”是与“函数”连在一起作为名词的。高阶函数(high-order functions)有点借用了数学中高阶的概念。当一个函数是以另一个函数作为参数,或者以另一个函数作为返回值的时候(或者两者兼备),就可以称作是高阶函数。举个例说:
typedef int (*int_func)(int); int f(int_func g, int x) { return g(x); }
这里,函数 f 就可以称作是一个高阶函数,因为它以另一个函数(实参 g )为参数。
高阶函数在函数式编程里面是很普遍,在命令式编程里面可能比较少提到,但对于C++来说,高阶函数已经不是新事物了。很多地方,例如C++的标准库自身,或者C++的巨牛库boost,对在C++中使用高阶函数已经有不同方面的支持。虽然目前还有很多方面我还没有触及到,但为了避免自己没记性忘掉,我还是准备把我不完全的认识先记录下来,日后继续慢慢更新。嗯,期待自己能写出一个系列呢~。
1. 函数的生成
生成函数?
假如现在有一个简单的函数(实际上是函数对象。这里用的是函数的纯抽象概念:只要表现得和函数一样的,都可以叫作“函”。下面的描述,如果不需要严格区分函数和函数对象,那么我一律称作函数),作用是做加法的:
template <class T> struct x_plus { T operator () (T op1, T op2) const { return op1+op2; } };
如果有两个int变量x, y,那么
x_plus<int>()(x, y);
得到的结果就是x+y。对于x_plus<int>()来说,它是一个二元函数。
通过把其中一个值变成常量,x_plus<int>()就可以表现像一个一元函数了:
x_plus<int>()(x, 1); // --------------- (1)
相当于计算x+1。
函数生成的过程与上面的这个过程是类似的。通过将一个函数的某一个变量绑定为另外一个变量,就可以得到一个新的函数。
然而严格地说,上面的函数调用(1)依旧是二元函数,只不过其中一个参数被固定了。对于一个一元函数,它被期望的调用方式应该是:
_i_am_an_unary_function_(x);
于是,在对于一些需要使用一元函数的场合,x_plus<int>()是无法使用的:
template <class unary_function> inline int _i_need_an_unary_func (unary_function f, int x) { return unary_function(x); } _i_need_an_unary_func ( x_plus<int>()(x, 1), 10 ); // error
std::bind1st和std::bind2nd
标准库的std::bind1st和std::bind2nd提供了一个解决方案,使得我们可以将x_plus<int>()转化为一个真正的一元函数:
#include<functional> using namespace std; _i_need_an_unary_func ( bind2nd(x_plus<int>(), 1), 10 ); // ok _i_need_an_unary_func ( bind2nd(x_plus<int>(), 1), 10 ); // ok
这里的x_plus<>实现于最开始时候定义的有点区别,会在后面说明。
std::bind2nd(x_plus<int>(), 1)和x_plus<int>()(x, 1)的区别在于前者将一个参数绑定到某个已有函数的某个实参上,从而生成一个新的函数,而后者是一个函数调用(而且是不合法的函数调用,这里的 x 会因为未定义而产生编译错误)。所以对于std::bind2nd(x_plus<int>(), 1),以下的调用是合法的:
bind2nd(x_plus<int>(), 1)(10); // ok, equivalent to function call: x_plus<int>()(10, 1)
std::bind1st和std::bind2nd是类似的,区别在于绑定参数的位置不一样:
bind1st(x_plus<int>(), 1)(10); // => x_plus<int>()(1, 10) bind2nd(x_plus<int>(), 1)(10); // => x_plus<int>()(10, 1)
因为std::bind1st和std::bind2nd的实现是类似的,我只考虑实现其中一个的基本原理:
bind1st返回的可以是普通函数指针或者函数对象,然而对于返回普通函数指针,实现起来似乎不怎么方便>*<,所以一般都是返回函数对象:
template <class F, class A1> struct x_bind1st_c { x_bind1st_c(const F& _f, A1 _a1) : f(_f), a1(_a1) { } return_type? operator () (argument_type? a2) const { return f(a1, a2); } private: F f; A1 a1; };
上面的实现还未完整,但不妨先进行分析。通过x_bind1st_c< x_plus<int>, int >(x_plus<int>(), 1),就可以产生一个函数对象func。函数对象里面已经定义了operator()(…)的成员,所以可以通过func(x)的方式来调用这个函数对象,达到预期的效果。
实现中还有部分地方没有明确:operator()(…)的返回类型是什么?实参a2的类型又是什么?或许可以假定为与A1相同,但这毕竟没有普适性:要求一个函数的返回值与实参类型相同、所有实参的类型都一样,这样的要求显然不合理。函数需要返回什么值,实参接受什么类型,是被绑定的函数决定的,如此考虑的话,直接从被绑定函数中获取是最好的方法。为了这样,需要对x_plus<>的定义稍微修改一下:
21 22 23 24 25 26 27 28 29 30 | template <class T> struct x_plus { typedef T return_type; typedef T arg1_type; typedef T arg2_type; T operator () (T op1, T op2) const { return op1+op2; } }; |
然后……重新写一次完整的x_bind1st_c的定义吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | template <class F> struct x_bind1st_c { typedef typename F::return_type return_type; typedef typename F::arg1_type arg1_type; typedef typename F::arg2_type arg2_type; x_bind1st_c(const F& _f, arg1_type _a1) : f(_f), a1(_a1) { } return_type operator () (arg2_type a2) const { return f(a1, a2); } private: F f; arg1_type a1; }; |
x_bind1st_c是一个类模板,使用的时候必须显式指定模板的参数。可以利用函数模板的自动推导简化使用:
16 17 18 19 20 | template <class F> inline x_bind1st_c<F> x_bind1st(F f, typename F::arg1_type a1) { return x_bind1st_c<F>(f, a1); } |
x_bind1st就这样完成了。(我并没有看过标准库的实现,所以我不保证这就是std::bind1st的实现方式。事实上我觉得标准库应该比我上面简单的实现考虑得更多,比如const和volatile关键字的修饰。不过我觉得基本原理应该是这样的。)
x_bind1st(算是简化+删节版的std::bind1st吧,所以下面把两者合在一起统称bind1st/bind2nd)的实现同时也提示了几点:
1、只能用在接受两个参数的函数对象上。
所以对于非二元的函数对象,无法使用bind1st/bind2nd;而且对于普通的函数,因为缺少return_type、arg1_type和arg2_type的定义,所以也不能直接使用;相同的情况存在于类成员函数指针中,不过成员函数指针的情况有点特殊,下面将会提及。
为了让bind1st/bind2nd可以为普通的函数绑定,标准库定义了一个ptr_fun,用来包装普通函数,使得bind1st/bind2nd可以用在普通函数上;对于类成员函数指针,标准库也定义了mem_fun/mem_fun_ref来包装,使得bind1st/bind2nd可以用在成员函数指针上:
int func1(int op1, int op2) { return op1+op2; } struct X { int func2(int op) { return op; } }; std::bind2nd(std::ptr_fuc(func1), 10)(20); std::bind1st(std::mem_fun(&X::func2), &x)(10);
成员函数X::func2只接受一个参数似乎和之前的说明矛盾。其实这是因为成员函数需要接受一个额外的参数(this指针的由来)。除去这个额外参数以后,就只能再接收一个了。
2、被绑定的参数只能是兼容于被绑定函数的可接收参数的类型(即存在隐式转换)。
简单的说就是你不能这样调用bind1st/bind2nd来实现生成类似于f(x)=(10+x)+x的函数:
std::bind1st( x_plus<int>(), std::bind1st(x_plus<int>(), 10) )(20);
原因很简单,因为经过bind1st/bind2nd后得到的是一个函数。除非原来的函数接受的就是函数类型的参数,否则,由于不存在隐式转换,这样的调用将产生错误。
* 或许可以考虑一下返回的是一个函数指针。但如果返回的是一个函数指针,那么这个函数的定义至少应该类似于:
R bind1st(A2 a2) { return &func_to_be_bound(a1_bound, a2); }
返回值、参数a1和a2的类型先不作考虑。
为了记录func_to_be_bound,bind1st必须定义为一个函数模板。这样的话就要求函数的参数列表中须新增用来接受func_to_be_bound的形参。这样的话又会破环bind1st的接口:
template <class f> R bind1st(A2 a2, f func_to_be_bound);
另一个可能的形式是这样:
template <class F, F f> R bind1st(A2 a2) { retrun f(a1, a2); }
这种形式中 f 是一个非类型的模板参数。如果 F 是一个指针类型的话, f 就是一个指针,也能算是整型的非模板参数,所以这样的形式是接受的。问题在于调用须以这样的形式进行:
bind1st(a2_arg);
和之前的bind1st的用法差别甚大。或许可以尝试写一个辅助函数来简化:
template <class F> R bind1st_aux(F f) { return &(bind1st<F, f>); }
然而这种方式实际使用中会产生编译错误,因为模板的实例化参数使用了函数参数。函数参数的值是在运行期才能决定的,而模板则要求在编译期完成实例化。
而且上面那样的函数模板无法接受非整型的函数对象作为模板参数,除非把函数对象定义为全局变量,并将函数对象的地址作为参数传入。局部变量因为储存在栈区,地址取决于栈的使用状况,故只能在运行期判明,不能用作模板参数(我认为理论上静态变量也是可以的,因为都是储存在静态区,地址是可以在编译期决定的。不过在gcc 3.4.5上只有使用全局对象时编译才能通过)。
暂时能想到的用普通函数指针来实现会存在的问题就这些,似乎对于普通函数指针来说限制是比较多的。如果哪位有用普通函数指针的不错的实现的话,请告诉我吧!
另外,这不知道算不算是函数对象比普通函数要灵活的一个例子呢?
>back<
最新评论