编写可适配的函数对象(Effective stl 条款40)

来源:互联网 发布:ruby ide windows 编辑:程序博客网 时间:2024/05/21 20:55
编写可适配的函数对象
Effective STL
Effective STL书中条款40提到,要使我们自己写的仿函数类可适配,仿函数类必须从类unary_function
或者binary_function继承,因为这两个类提供了对象适配器需要的typedef
 
list<Widget> widgets;
//...

list<Widget>::reverse_iterator i1 =      //找到最后一个不
find_if(widgets.rbegin(), widgets.rend(),//合阈值的widget
           not1(MeetsThreshold<int>(10)));//不管意味着什么)
Widget w(构造函数实参);
list
<Widget>::iterator i2 =              //找到第一个在由

find_if(widgets.begin(), widgets.end(), //WidgetNameCompare定义
           bind2nd(WidgetNameCompare(), w);//的排序顺序上先于w的widget
  
如果我们没有把仿函数类继承自unary_functionbinary_function,这些例子都不能编译,因为not1bind2nd都只和可适配的函数对象合作。书上告诉我们的理由是:对象适配器需要unary_functionbinary_function提供的typedef才能工作,所以我们的仿函数类必须继承自这两个类。
按照这个理由编写的仿函数类确实能很好的配合类适配器工作,但是,这两个神秘的类到底做了些什么呢?为了搞清楚这两个的类,最好的办法还是把源码搬出来,看看它到底做了些什么。
 
plusbind1st为例来作分析。plus是一个可适配的类,继承自binary_function,可以和适配器bind1st合作,这些函数和类都在functional头文件中。
先来这个unary_function类:
 
// TEMPLATE STRUCT unary_function
template<class _Arg,class _Result>
struct unary_function
{    // base class for unary functions
  
     typedef _Arg argument_type;//参数类型
     typedef _Result result_type;//函数返回类型
 
}
;
 
原来这么简单,只不过是两个typedef而已,一个是一元函数的参数类型别名,另一个是返回类型。接着看看binary_function类:
 
// TEMPLATE STRUCT binary_function
template<class _Arg1,class _Arg2,class _Result>
struct binary_function
{    
// base class for binary functions
   
     typedef _Arg1 first_argument_type;
     typedef _Arg2 second_argument_type;
     typedef _Result result_type;
   
};
  
同样简单,仅仅是几个别名而已,什么正经事都没做。
 
下面是functional中仿函数类plus的定义。plus是一个二元操作仿函数类,两个操作数类型都是_Ty类型,返回类型是_Ty类型,plusbinary_function继承,所以会实例化了一个binary_function基类对象,这个基类定义了三个类型别名:两个参数类型别名first_argument_typesecond_argument_type,还有一个返回类型的别名result_type
 
// TEMPLATE STRUCT plus
template<class _Ty>
struct plus: public binary_function<_Ty, _Ty, _Ty> //构造基类
{    // functor for operator+
  
     _Ty operator()(const _Ty& _Left, const _Ty& _Right) const
     {    
// apply operator+ to operands
           return (_Left + _Right);
     }
  
};
 
再来看对象适配器类binder1st的定义,这个类就是我们bind1st函数的返回类型,bind1st函数将在下面介绍:
 
// TEMPLATE CLASS binder1st
template<class _Fn2>
class binder1st://binder1st继承自unary_function,所以它本身为一元函数对象类
     public unary_function<typename _Fn2::second_argument_type,
                                      typename _Fn2::result_type>//设置基类
 
{    // functor adapter _Func(stored, right)
  
public:
     typedef unary_function<typename _Fn2::second_argument_type,
                                                                             typename _Fn2::result_type> _Base;
     typedef typename _Base::argument_type argument_type;
     typedef typename _Base::result_type result_type;
  
     binder1st(
const _Fn2&
 _Func,
           
const typename _Fn2::first_argument_type&
 _Left)
           : op(_Func), value(_Left)
     {    
// construct from functor and left operand

     }
   
     result_type 
operator()(const argument_type& _Right) const//调用操作符参数为const版本

     {    // apply functor to operands
           return (op(value, _Right));//将自己接收的参数当作二元函数对象op的第二个参数调用
     }
   
     result_type 
operator()(argument_type& _Right) const//调用操作符,参数为非const版本

     {    // apply functor to operands
           return (op(value, _Right));//调用被绑定的函数对象op,第一个参数为类中保存的
                                             
//绑定值value

     }
   
protected
:
     _Fn2 op;   
// the functor to apply

     typename _Fn2::first_argument_type value; // the left operand
};
   
binder1st类从unary_function类继承,说明我们可以将binder1st当作一个一元函数来调用,它通过将一个二元函数对象的第一个参数保存,从而实现了二元函数类到一元函数类的转换。binder1st类有两个数据成员,其中一个为被绑定的二元函数对象副本,另一个就是二元函数对象的第一个参数。所以binder1st类的其实原理很简单:binder1st类本身是一个一元函数对象,它保存了需要被绑定的二元函数对象以及二元函数对象的第一个参数值,而每次对binder1st的调用操作,实际上都间接的转换到了保存的二元函数对象的调用。
binder1st类模板的关键并不是原理问题,而在于实现binder1st时,我们并不能从模板参数_Fn2中推断出被绑定二元函数对象类的参数类型以及返回类型,当然也就没有办法生成binder1st类了。这时binary_function类的作用就显现出来了,binder1st假定模板实参为从binary_function继承而来,这样,_Fn2::second_argument_type就是_Fn2的第二参数类型,同样,_Fn2::first_argument_type value是第一个参数类型,_Fn2::result_type为返回类型。
所以说,适配器类binder1st是通过假设模板形参_Fn2类存在三个typedef:first_argument_type,
second_argument_type,result_type,分别表示两个形参一个返回值,通过这些typedef就能实现binder1d
自身的定义了。
 
分析下来,其实为了写一个可被适配仿函数类,也并不是非得从binary_function或者unary_function继承而来,只要我们定义了相应的typedef就可以了,比如来定义一个我们自己的my_plus类:
 
当然,我们这种方法没有stl里用继承来的方便,这里仅仅是为了说明问题而举的例子。
template<typename _Ty>
struct my_plus//没有继承自binary_function
{
     typedef _Ty first_argument_type;
//只要我们自己定义了这些typedef就行

     typedef _Ty second_argument_type;
     typedef _Ty result_type;
  
     _Ty 
operator()(const _Ty &_Left,const _Ty &_Right)const

     {
           
return (_Left+_Right);
     }
  
};
  
transform(vecd.begin(),vecd.end(),back_inserter(results),
bind1st(my_plus
<int>(),30));//OK,my_plus也是一个可适配仿函数类,
                                        //虽然并//没有继承自binary_function
 
最后一个需要实现的函数是我们熟悉的函数适配器bind1st(),它是一个函数模板,第一个参数为需要适配的二元函数对象类,而将该二元函数对象的第一个参数作为bind1st函数的第二个参数传进来实现绑定:
 
// TEMPLATE FUNCTION bind1st
template<class _Fn2,class _Ty> inline
binder1st
<_Fn2> bind1st(const _Fn2& _Func, const _Ty&
 _Left)
{    
// return a binder1st functor adapter
  
     typename _Fn2::first_argument_type _Val(_Left);//强制类型转换
     return (std::binder1st<_Fn2>(_Func, _Val));
  
}
  
bind1st()相当于一个类工厂,把一个二元函数对象转换成为一元函数对象binder1st
函数模板bind1st()用了两个模板参数_Fn2_Ty来实现,这样作有一个好处,那就是第二个函数实参不必完全匹配typename _Fn2::first_argument_type 类型,只要_Ty能转换为_Fn2::first_argument_type就可以适配成功。
 
我们可以这样实现一个自己的my_bind1st函数,只用了一个模板形参_Fn2,第二个函数形参绑定为typename _Fn2::first_argument_type类型:
 
template<typename _Fn2> inline
binder1st<_Fn2> my_bind1st(const _Fu2 &func, _Fn2::first_argument_type val)
{
 
     
return binder1st<_Fn2>
(func,val);
 
}
//只用一个模板形参就好了,first_argument_type不就是绑定的第一个参数类型吗?为什么stl中单独定义了一个_Ty类型
 
我们这个函数硬性规定了第二个函数实参必须为_Fn2::first_argument_type类型,这使得函数适配很不灵活,考虑有这样一个类D
 
class D
{
     friend D 
operator+(const D &,const D &
);
public
:
     
explicit D(int i=0):num(i){}//显示构造函数,避免构造函数被隐式调用
 
private:
     
int
 num;
 
};
 
operator+(const D &left,const D &
right)
{
     
return D(left.num+
right.num);
}
 
transform(vecd.begin(),vecd.end(),back_inserter(results),
            my_bind1st(plus
<D>(),30));//错误,不能将int转换为D类型,因为构造函数是explicit的
 
transform(vecd.begin(),vecd.end(),back_inserter(results),
            bind1st(plus
<D>(),30));//OK,实例化bind1t<D,int>类型
 
通过my_bind1st适配成功的可能性比bind1st小,因为它对类型的限定比较严格,而bind1st通过放宽适配
条件保证适配成功,函数内部通过强制类型转换来得到需要的类型:
 
typename _Fn2::first_argument_type _Val(_Left);//强制类型转换
 
总结:
适配器类默认了几个假设:
对一元函数对象类假设存在rgument_typeresult_type定义,分别表示一元函数对象的参数和返回类型
对二元函数对象假设存在first_argument_type, second_argument_type, result_type,分别表示第一、第二个参数类型以及返回类型。
所以只要我们定义了这些类型别名,就能保证我们的函数对象被适配成功。可以像上面介绍的my_plus类一样自己定义这几个typedef,但更简单方法的是从unary_functionbinary_function继承出我们的函数对象。
 
原创粉丝点击