STL中有很多算法函数(包含在algorithm)都需要提供一个称为函数对象(Function Object)类型的参数。为什么需要这样一个参数?
首先这些算法通常是对容器(即存放一堆同类元素的对象,比如list、vector)内的诸多元素进行某种操作,比如sort是对该容器内的这些元素进行排序、find_if是查找该容器内符合某种条件的元素、count_if是计算该容器内符合某种条件的元素总数。
为了算法可复用,其中的子操作应该是参数化的,比如sort的排序原则(顺序、逆序)、find_if/count_if的匹配条件。函数对象就是用来描述这些子操作的。
例S-1
//////////////////////////////////////////////////////////////////////////
1 #include <iostream>
2 #include <iterator>
3 #include <algorithm>
4 #include <functional>
5
6 using namespace std;
7 void Add(int & ri)
8 {
9 ri += 5;
10 }
11 struct Sub : public unary_function<int,void>
12 {
13 void operator() (int & ri)
14 {
15 ri -= 5;
16 }
17 };
18
19 int main()
20 {
21 int a[10]={0,1,2,3,4,5,6,7,8,9};
22 copy(a,a+10,ostream_iterator<int>(cout," "));
23 cout << endl;
24 for_each(a,a+10,Add);
25 copy(a,a+10,ostream_iterator<int>(cout," "));
26 cout << endl;
27 for_each(a,a+10,Sub());
28 copy(a,a+10,ostream_iterator<int>(cout," "));
29 cout << endl;
30 }
//////////////////////////////////////////////////////////////////////////
先看看for_each函数的原型:
template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator first, InputIterator last, UnaryFunction f);
前两个参数分别说起始元素和终止元素的地址,第三个参数是一个一元函数。
例S-1中,Add和Sub()都是函数对象,其中Add就是一个全局函数,在24行把Add作为for_each的参数。
在27行,用Sub结构类型实例化一个函数对象传给for_each作参数。Sub从基类(事实上是个结构)unary_function派生,unary_function的定义如下:
template <class _Arg, class _Result>
struct unary_function {
typedef _Arg argument_type;
typedef _Result result_type;
};
函数对象可以是一个普通函数、可以是函数指针,可以跟其对象一样是某个类的一个实例,但该类必须定义圆括号“()”运算符。
函数对象描述的是一种操作,它也可以有参数,根据函数对象的参数个数,STL算法用到的主要有三类:
- 没有参数的函数对象(Generator),相当于“f()”,比如:
vector<int> V(10);
generate(V.begin(), V.end(), rand);
其中的“rand”就是属于Generator。 - 一个参数的函数对象、一元函数(Unary Function),相当于“f(x)”,比如上面的“Add”,“Sub()”也返回一个一元函数对象。STL中用unary_function定义了一元函数基类。
- 两个参数的函数对象、二元函数(Binary Function),相当于“f(x,y)”,比如:
struct less_mag : public binary_function<double, double, bool>
{
bool operator()(double x, double y) { return fabs(x) < fabs(y); }
};
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;
};
函数对象也有返回值,返回值是bool型的函数对象称谓词(Predicate),通常称返回bool的一元函数为谓词,返回bool的二元函数为二元谓词。
有时候,需要把一个二元函数对象转换为一元函数对象、或者组合几个二元谓词为一个谓词。
例S-2
//////////////////////////////////////////////////////////////////////////
1 #include <iostream>
2 #include <iterator>
3 #include <algorithm>
4 #include <functional>
5
6 using namespace std;
7 int Mul(int ri,int multiplier)
8 {
9 ri *= multiplier;
10 return ri;
11 }
12
13 int main()
14 {
15 int a[10]={0,1,2,3,4,5,6,7,8,9};
16 int b[10];
17 copy(b,b+10,ostream_iterator<int>(cout," "));
18 cout << endl;
19 transform(a,a+10,b,bind2nd(ptr_fun(Mul),5));
20 copy(b,b+10,ostream_iterator<int>(cout," "));
21 cout << endl;
22 }
//////////////////////////////////////////////////////////////////////////
例S-2中,19行调用ptr_fun把一个普通全局函数转换成一个二元函数对象,并调用bind2nd函数把该二元函数对象的第二个参数multiplier绑定为5成为一个一元函数对象。
例S-2就是把a[i]*5存入b[i]中,可以使用:
transform(a,a+10,b,bind2nd(ptr_fun(Mul),7));
把a[i]*7存入b[i]中。
ptr_fun有两个原型:
template <class Arg, class Result>
pointer_to_unary_function<Arg, Result>
ptr_fun(Result (*x)(Arg));
template <class Arg1, class Arg2, class Result>
pointer_to_binary_function<Arg1, Arg2, Result>
ptr_fun(Result (*x)(Arg1, Arg2));
这里显然用到第二个原型:
template <class _Arg1, class _Arg2, class _Result>
inline pointer_to_binary_function<_Arg1,_Arg2,_Result>
ptr_fun(_Result (*__x)(_Arg1, _Arg2)) {
return pointer_to_binary_function<_Arg1,_Arg2,_Result>(__x);
}
所以上面的语句完全可以写成:
transform(a,a+10,b,bind2nd(pointer_to_binary_function<int,int,int>(Mul),7));
“pointer_to_binary_function<_Arg1,_Arg2,_Result>”是个模板类,它的构造函数以一个二元函数指针为参数:
template <class _Arg1, class _Arg2, class _Result>
class pointer_to_binary_function : public binary_function<_Arg1,_Arg2,_Result> {
protected:
_Result (*_M_ptr)(_Arg1, _Arg2);
public:
pointer_to_binary_function() {}
explicit pointer_to_binary_function(_Result (*__x)(_Arg1, _Arg2)) : _M_ptr(__x) {}
_Result operator()(_Arg1 __x, _Arg2 __y) const {
return _M_ptr(__x, __y);
}
};
它从binary_function派生,有个保护成员变量_M_ptr保存二元函数指针,定义了“()”运算符,其中调用_M_ptr指向的二元函数。
bind2nd也是个内联函数:
template <class _Operation, class _Tp>
inline binder2nd<_Operation>
bind2nd(const _Operation& __fn, const _Tp& __x)
{
typedef typename _Operation::second_argument_type _Arg2_type;
return binder2nd<_Operation>(__fn, _Arg2_type(__x));
}
所以刚才的语句又可以写成:
transform(a,a+10,b,binder2nd<pointer_to_binary_function<int,int,int> >(pointer_to_binary_function<int,int,int>(Mul),5));
“binder2nd<_Operation>”也是一个模板类,“_Operation”在这里被“pointer_to_binary_function<int,int,int>”替代,其构造函数有两个参数,第一个是_Operation类对象,第二个是_Operation的第二个参数类型常量:
template <class _Operation>
class binder2nd : public unary_function<typename _Operation::first_argument_type, typename _Operation::result_type> {
protected:
_Operation op;
typename _Operation::second_argument_type value;
public:
binder2nd(const _Operation& __x, const typename _Operation::second_argument_type& __y) : op(__x), value(__y) {}
typename _Operation::result_type operator()(const typename _Operation::first_argument_type& __x) const {
return op(__x, value);
}
};
它从unary_function派生,也有个保护成员变量op保存实例化时传入的_Operation对象,并有保护成员变量value保存被绑定的第二个参数值,也定义了“()”运算符,其中调用二元函数对象op的“()”运算符。
从“transform(a,a+10,b,bind2nd(ptr_fun(Mul),5));”到“transform(a,a+10,b,binder2nd<pointer_to_binary_function<int,int,int> >(pointer_to_binary_function<int,int,int>(Mul),5));”,虽然从语法上复杂了(事实上也没有人会这样写C++代码),但却弄清了函数对象的来龙去脉。
它充分利用了C++的模板特性,当然也依靠了其它一些关键字,如typedef、typename。
象pointer_to_binary_function、binder2nd这样的模板类比较特殊,它们对其它函数对象进行修改或组装生成一个新的函数对象。比如pointer_to_binary_function它把普通全局函数Mul改成一个二元函数对象,binder2nd把pointer_to_binary_function实例化出来的二元函数对象和一个常量5组装生成一个一元函数对象。这样的模板类在STL中被称为函数对象适配器(function object adaptors)。
STL中包含许多预定义的函数对象,其中有:- 算术操作:plus, minus, multiplies, divides, modulus, 和 negate,例:
assert(V2.size() >= V1.size() && V3.size() >= V1.size());
transform(V1.begin(), V1.end(), V2.begin(), V3.begin(),modulus<int>());把V1[i]%V2[i]存入V3[i]。
- 比较操作:equal_to, not_equal_to, greater, less, greater_equal, 和 less_equal,例:
list<int>::iterator first_nonnegative = find_if(L.begin(), L.end(), bind2nd(greater_equal<int>(), 0));
在L中找到第一个大于等于0的元素。
- 逻辑操作:logical_and, logical_or, 和 logical_not,例:
char str[MAXLEN];
const char* wptr = find_if(str, str + MAXLEN,compose2(logical_or<bool>(),bind2nd(equal_to<char>(), ' '),bind2nd(equal_to<char>(), '/n')));在str中找到第一个空格或行尾。
以上三类都是模板类,要生成一个函数对象需要实例化。同时在使用时,需要注意:
- 算法需要的参数是什么类型的函数对象,是一元函数还是二元函数。
- 输入容器内元素类型是否与函数对象参数兼容,输出容器内元素是否与函数对象返回值兼容。
- 修改、组装已有函数对象生成新的函数对象:unary_compose, binary_compose, unary_negate, binary_negate, binder1st, binder2nd, pointer_to_unary_function, pointer_to_binary_function, mem_fun_t, mem_fun_ref_t, mem_fun1_t 和mem_fun1_ref_t。这类函数对象就是所谓的函数对象适配器,由于书写生涩,一般不会直接实例化,都有对应的辅助函数(一般都是内联型)简化调用。
函数对象模板类名 | 辅助函数名 | 说明 |
unary_compose | compose1 | unary_compose(const AdaptableUnaryFunction1& f, const AdaptableUnaryFunction2& g); 生成一个一元函数对象,操作为“f(g(x))”;vc6/7自带stl没有定义。 |
binary_compose | compose2 | binary_compose(const AdaptableBinaryFunction& f, const AdaptableUnaryFunction1& g1, const AdaptableUnaryFunction1& g2); 生成一个一元函数对象,操作为“f(g1(x), g2(x))”;vc6/7自带stl没有定义。 |
unary_negate | not1 | unary_negate(const AdaptablePredicate& pred);
一元谓词取非。 |
binary_negate | not2 | binary_negate(const AdaptableBinaryPredicate& pred);
二元谓词取非。 |
binder1st | bind1st | binder1st(const AdaptableBinaryFunction& F, AdaptableBinaryFunction::first_argument_type c); 把二元函数对象的第一个参数绑定为c,生成一个一元函数对象。 |
binder2nd | bind2nd | binder2nd(const AdaptableBinaryFunction& F, AdaptableBinaryFunction::second_argument_type c); 把二元函数对象的第二个参数绑定为c,生成一个一元函数对象。 |
pointer_to_unary_function | ptr_fun | 把普通全局函数或全局函数指针(只能有1个或2个参数)转换成函数对象。
ptr_fun有两个原型分别生成pointer_to_unary_function对象和pointer_to_binary_function对象。 |
pointer_to_binary_function | ||
mem_fun_t | mem_fun | 把某个类的成员函数(只能有0个或1个参数,因为类成员函数的第一个参数已定义为this指针)转换成函数对象。
mem_fun有两个原型分别生成mem_fun_t对象(是个一元函数对象)和mem_fun1_t对象(是个二元函数对象)。 |
mem_fun1_t | ||
mem_fun_ref_t | mem_fun_ref | 类似mem_fun,区别如下:
struct T{ list<T*> lstT; list<T> lstT; |
mem_fun1_ref_t | ||
例S-3
//////////////////////////////////////////////////////////////////////////
#include <string>
#include <list>
#include <algorithm>
#include <iostream>
using namespace std;
class T
{
public:
T(int i1,int i2,string s){score1 = i1;score2 = i2;name = s;totalscore = i1+i2;}
int score1;
int score2;
int totalscore;
string name;
void print()
{
cout << name << "/t/t" << score1 << "/t/t" << score2 << "/t/t" << totalscore << endl;
}
};
int main(int argc,char ** argv)
{
list<T> lstT;
lstT.push_back(T(34,75,"TOM"));
lstT.push_back(T(23,84,"JIM"));
lstT.push_back(T(43,63,"JOHN"));
lstT.push_back(T(61,69,"MIKE"));
lstT.push_back(T(21,90,"ROBIN"));
lstT.push_back(T(59,87,"SALLY"));
lstT.push_back(T(54,72,"LINDA"));
lstT.push_back(T(43,79,"JAMES"));
for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print));
cout << endl;
list<T*> lstTa;
lstTa.push_back(new T(34,75,"tom"));//"guw" in vi can chanage a word lowercase.
lstTa.push_back(new T(23,84,"jim"));
lstTa.push_back(new T(43,63,"john"));
lstTa.push_back(new T(61,69,"mike"));
lstTa.push_back(new T(21,90,"robin"));
lstTa.push_back(new T(59,87,"sally"));
lstTa.push_back(new T(54,72,"linda"));
lstTa.push_back(new T(43,79,"james"));
for_each(lstTa.begin(),lstTa.end(),mem_fun(&T::print));
cout << endl;
}
//////////////////////////////////////////////////////////////////////////
阅读STL源码是提升C++经验值的有效途径,虽然其书写风格让不少人觉得别扭,但认真阅读也会让人受益匪浅。下面是我从STL源码中吸取精华、自己实现的一个函数对象适配器binder3rd。
STL提供了把一个二元函数对象转换成一元函数对象的函数(bind1st/bind2nd),但我有时需要把一个三元函数对象转换成二元函数对象,STL没有提供这样的函数对象适配器。
先定义一个类型描述三元函数:
template <class _Arg1, class _Arg2, class _Arg3, class _Result> struct ternary_function { typedef _Arg1 first_argument_type; typedef _Arg2 second_argument_type; typedef _Arg3 third_argument_type; typedef _Result result_type; }; |
某个类的二元成员函数应该是一个三元函数,所以二元成员函数应该从ternary_function派生。
template <class _Ret, class _Tp, class _Arg1, class _Arg2> class const_mem_fun2_ref_t : public ternary_function<_Tp,_Arg1,_Arg2,_Ret> { public: explicit const_mem_fun2_ref_t(_Ret (_Tp::*__pf)(_Arg1,_Arg2) const) : _M_f(__pf) {} _Ret operator()(const _Tp& __r, _Arg1 __x, _Arg2 __y) const { return (__r.*_M_f)(__x,__y); } private: _Ret (_Tp::*_M_f)(_Arg1,_Arg2) const; }; |
binder3rd的目的是把一个三元函数对象转换成二元函数对象,所以binder3rd要从binary_function派生。
template <class _Operation> class binder3rd : public binary_function<typename _Operation::first_argument_type,typename _Operation::second_argument_type,typename _Operation::result_type> { protected: _Operation op; typename _Operation::third_argument_type value; public: binder3rd(const _Operation& __x,const typename _Operation::third_argument_type& __y) : op(__x), value(__y) {} typename _Operation::result_type operator()(const typename _Operation::first_argument_type& __x,const typename _Operation::second_argument_type& __y) const {return op(__x, __y, value); } }; |
完整的代码如下:
#include <string> #include <list> #include <vector> #include <stack> #include <queue> #include <map> #include <set> #include <algorithm> #include <iostream> #include <sstream> #include <cstdio> #include <cctype> #include <cmath> using namespace std; template <class _Arg1, class _Arg2, class _Arg3, class _Result> struct ternary_function { typedef _Arg1 first_argument_type; typedef _Arg2 second_argument_type; typedef _Arg3 third_argument_type; typedef _Result result_type; }; template <class _Ret, class _Tp, class _Arg1, class _Arg2> class const_mem_fun2_ref_t : public ternary_function<_Tp,_Arg1,_Arg2,_Ret> { public: explicit const_mem_fun2_ref_t(_Ret (_Tp::*__pf)(_Arg1,_Arg2) const) : _M_f(__pf) {} _Ret operator()(const _Tp& __r, _Arg1 __x, _Arg2 __y) const { return (__r.*_M_f)(__x,__y); } private: _Ret (_Tp::*_M_f)(_Arg1,_Arg2) const; }; template <class _Operation> class binder3rd : public binary_function<typename _Operation::first_argument_type,typename _Operation::second_argument_type, typename _Operation::result_type> { protected: _Operation op; typename _Operation::third_argument_type value; public: binder3rd(const _Operation& __x,const typename _Operation::third_argument_type& __y) : op(__x), value(__y) {} typename _Operation::result_type operator()(const typename _Operation::first_argument_type& __x,const typename _Operation::second_argument_type& __y) const { return op(__x, __y, value); } }; class T { public: T(int i1,int i2,string s){score1 = i1;score2 = i2;name = s;totalscore = i1+i2;} int score1; int score2; int totalscore; string name; bool CompareBy(T aT,int type) const { switch(type) { case 0://score1 return (score1 > aT.score1); break; case 1://score2 return (score2 > aT.score2); break; case 2://totalscore return (totalscore > aT.totalscore); break; default: break; } return false; } void print() { cout << name << "/t/t" << score1 << "/t/t" << score2 << "/t/t" << totalscore << endl; } }; int main(int argc,char ** argv) { list<T> lstT; lstT.push_back(T(34,75,"TOM")); lstT.push_back(T(23,84,"JIM")); lstT.push_back(T(43,63,"JOHN")); lstT.push_back(T(61,69,"MIKE")); lstT.push_back(T(21,90,"ROBIN")); lstT.push_back(T(59,87,"SALLY")); lstT.push_back(T(54,72,"LINDA")); lstT.push_back(T(43,79,"JAMES")); for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print)); cout << endl; lstT.sort( binder3rd<const_mem_fun2_ref_t<bool,T,T,int> >(const_mem_fun2_ref_t<bool,T,T,int>(&T::CompareBy),0) ); for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print)); cout << endl; lstT.sort( binder3rd<const_mem_fun2_ref_t<bool,T,T,int> >(const_mem_fun2_ref_t<bool,T,T,int>(&T::CompareBy),1) ); for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print)); cout << endl; lstT.sort( binder3rd<const_mem_fun2_ref_t<bool,T,T,int> >(const_mem_fun2_ref_t<bool,T,T,int>(&T::CompareBy),2) ); for_each(lstT.begin(),lstT.end(),mem_fun_ref(&T::print)); cout << endl; } |