其他语言

本类阅读TOP10

·基于Solaris 开发环境的整体构思
·使用AutoMake轻松生成Makefile
·BCB数据库图像保存技术
·GNU中的Makefile
·射频芯片nRF401天线设计的分析
·iframe 的自适应高度
·BCB之Socket通信
·软件企业如何实施CMM
·入门系列--OpenGL最简单的入门
·WIN95中日志钩子(JournalRecord Hook)的使用

分类导航
VC语言Delphi
VB语言ASP
PerlJava
Script数据库
其他语言游戏开发
文件格式网站制作
软件工程.NET开发
Boost源码剖析之:泛型函数指针类boost::function 之生死因果

作者:未知 来源:月光软件站 加入时间:2005-2-28 月光软件站

                    

                                                  刘未鹏(pp_liu@msn.com)

             20039月发表于《程序员》

前奏

   如你所知,Boost库是个特性完备,且具备工业强度的库,众多C++权威的参与使其达到了登峰造极的程度。尤其泛型的强大威力在其中被发挥得淋漓尽致,令人瞠目结舌。

   然而弱水三千,我们只取一瓢饮。下面,我试图从最单纯的世界开始,一步一步带领你进入源码的世界,去探究function的生死因果,来龙去脉,以及其内部的精微结构。

  通常 ,在单纯的情况下,对函数的调用简单而且直观,像这样:

      int fun(int someVal);

      int main(){

         fun(10);

      }

  然而你可能需要在某个时刻将函数指针保存下来,并在以后的另一个时刻调用它,像这样:

     int fun(int);

     typedef int (*func_handle)(int);

     int mian(){

        func_handle fh=fun;

        ...  //do something

        fh(10);

     }

  然而,如果fun形式为void fun(int)呢?如你所见,fun可能有无数种形式,如果对fun的每一个形式都typedef一个对应的func_handle,则程序员会焦头烂额,不胜其扰,代码也可能变得臃肿和丑陋不堪,甚至如果fun是仿函数呢?

  幸运的是C++泛型可以使代码变得优雅精致,面对无数种的可能,泛型是最好的选择。

  因此,你只是需要一个能够保存函数指针的泛型模板类(对应于Command模式),因为泛型编程有一个先天性的优势----可以借助编译器的力量在编译期根据用户提供的型别信息化身千万(具现化),所以一个泛型的类可以有无限个具现体,也就是说可以保存无限多种可能型别的函数或类似函数的东西(如,仿函数)。这个类(Boost库中的类名为function)与函数指针相比应该有以下一些优势:

1.同一个function对象应能够接受与它形式兼容的所有函数和函数对象,例如:

                 int gf(int);       //这是个函数

                 struct functor   //这是个仿函数类,它与gf形式兼容,即调用的方式都是func(someIntVal)

                 {                  //并都返回int

                    int operator()(int){}

                  };

                 functor gf1; //仿函数对象

          function<int(int)> func; //function应能够接受所有具有int (int)形式的函数或仿函数对象

          func=gf;  //接受gf

          func(10); //调用gf(10)

          func=gf1; //也能接受gf1

          func(10); //调用gf1(10)

2.function应能够和参数绑定以及其它function-construction库协同工作。例如,function应该也能够接受std::bind1st返回的仿函数。这一点其实由第一点已经有所保证。

3.当接受的一个空的仿函数对象被调用的时候function应该有可预期的行为。

显然,第一点是我们的重点,Boost库通过使用所谓的invoker非常巧妙地实现了这点(后面你会看到,invoker的使用也非常巧妙的避免了一些不安全因素)

 

探险

好吧,准备好,我们要出发了,进行深入源码世界的探险。

先看一个function的最简单的使用:

   int g(int); //为了让代码简单,假设g有定义,以后的代码都会如此

   function<int(int)> f(g);

   f(0);

间奏曲

虽然这个间奏未免早了点儿,但是为了让你以后不会带着迷惑,这显然是必要的。请保持耐心。

或许你会对模板参数int(int)感到陌生,其实它是个函数型别函数g的型别就是int(int),之所以说函数g而不直接说g是因为我不想让你混淆它们,编译器认为g&g的型别为int (*)(int),举个例子:

   template<class T>

   void test_func_type(T ft)

   {

       static_cast<int>(ft);

   }

       test_func_type(g);   //g的声明同上

       test_func_type(&g);

当然,这样的代码不能通过编译,因为static_cast<>显然不会让一个函数指针转换为int,然而我们就是要它通不过编译,这样我们可以从编译错误中窥视到编译器“型别推导”的一些秘密,对于以上的代码编译器会抱怨说无法从int (*)(int)转换为int,不管是传g还是&g,很显然,编译器的型别推导将g&g的型别都认为是int (*)(int)

函数型别乃是个极其特殊的型别,我不知道有哪种变量的型别可以被推导为函数型别(正如上面所展示的,如果你将函数名传参,结果编译器将其推导为函数指针),然而如果你像定义int i;一样“定义”它,那么你实际上是在声明一个函数,例如:

   typedef int f_t(int);     

   typedef int (*pf_t)(int);

   f_t fun;  //声明了一个函数fun,等同于 int fun(int);

   pf_t pfun; //这定义了一个函数指针

所以,既然我们要代表int(int)型别的函数及仿函数对象,就应该将函数的型别作为模板参数,这样既减少了书写量(虽然微不足道),也使代码更直观。(Andrei AlexandrescuLoki[2]里也有类似的function设施,不过它的模板参数采用了更精致的TypeList设施,然而事实表明那其实没有必要,编译器完全可以从函数型别中推导出函数的返回值型别以及各参数型别,这仰赖于神奇的模板偏特化)

 

继续旅程

好吧,回过神来,我们还有更多的地带要去探究。

function<int(int)>实际上进行了模板偏特化,Boost库给function的类声明为:

       template<typename Signature,

           typename Allocator = std::allocator<function_base> > //allocator不是本文的重点,所以不作介绍

   class function;

事实上这个声明不能起作用,真正起作用的是偏特化版本。

Boost库给function<R(T0)>这个偏特化版本的function源码像这样(实际上在boost源代码中你看不到模板参数T0的声明,也看不到function1,它们被宏替换掉了,那些精巧的宏是为了减小可见的代码量,至于它们的细节则又是一个世界,以下代码可看作对将那些令人眼花缭乱的宏展开后所得到的代码,具有更好的可读性)

  摘自:boost/function/function_template.hpp

  template<typename R,typename T0,typename Allocator>

  class function<R(T0),Allocator>

       :public function1<R,T0,Allocator> //极其重要的基类,绝大部分实现都在其中,在下面讨论

  {

        typedef function1<R,T0,Allocator> base_type;

        typedef function selftype;  //内部typedef,随模板一同具现化,相当于

                                    //typedef function<R(T0),Allocator> selftype;

        struct clear_type{};        //马上你会看到这个蹊跷的类型定义的作用

   public:

       function() : base_type() {}  //默认构造

            template<typename Functor>  //模板化的构造函数,为了能够接受形式兼容的仿函数对象

         1  function(Functor  f, typename detail::function::enable_if<    //这个冗长的typename

                                 (::boost::type_traits::ice_not<          //以及下面的function(clear_type*)

                                 (is_same<Functor, int>::value)>::value), //构造函数的意图在下面作为注释1.

                                 int>::type = 0) base_type(f){}

            function(clear_type*) : base_type() {}  //这个构造函数的作用在下面解释   

            self_type& operator=(const self_type& f)  //同类型function对象之间应该能够赋值

            {

                self_type(f).swap(*this); //这儿用到了swap惯用手法,即构造一个临时对象并将它与

                                          //*this进行底层数据交换,交换完后临时对象生命期也已结

                                          //束,于是被析构,然而*this已经拥有了该有的底层数据,

                                          //这种手法能使代码简洁。swap是基类的函数,交换底层数据

                return *this;             //至于底层数据是什么,后面你会明了

            }

            //下面还理所当然有operator=的定义,几乎与模板化构造同出辄,而且为了使代码保持简洁,所以省略掉

            ...

         };

       注释1模板构造函数和模板operator=中出现的detail::function::enable_if<>;template<bool cond,typename T> enable_if;

                 condtrue时在内部有typedef T type;confalse时则没有任何typedefs, ice_not<>相当于将bool值取反。所以:

               Functor为函数或仿函数型别(不是int型别)时:

    is_same<Functor,int>::valuefalse

                   ice_not<(is_same<Functor,int>::value)>::valuetrue

                   typename detail::function::enable_if<(::boost::type_traits::ice_not<(is_same<Functor, int>::value)>::value),

                int>::type其实就是typename enable_if<true,int>::type也就是int

                   则那个模板构造函数其实也就是template<typename Functor> function(Functor f,int =0) ...里面的无名缺省int型参数

                   不起任何作用,至于为什么多此一举,我会马上为你揭开谜底。

               Functorint型别时:

                   按照上面的推导过程,其实是typename enable_if<false,int>::type,然而前面说过当enable_if<>的第一个模板参数为false

                   时它的内部其实空空如也,所以该模板构造不能具现化。也就是说你写function f=10;这样的语句通不过编译,而事实上你

                   也确实不能对哪个函数指针这样赋值,但是,等等!,你说:函数指针却应该可以被赋为0(NULL)。事实上任意型别的

                   指针都可以被赋为0,然而上面的推导表明当Functorint型时这个模板构造等于不存在,又如何赋为零呢?答案就在下面。

               function(clear_type*):base_type(){}显然可以接受0为参数,因为0可以赋给任意型别的指针。所以当你写function<int(int)>

               f=0;时其实编译器暗中调用的是这个构造函数,它进行缺省初始化。然而如果你写function f=10;编译器会抱怨不能将int型的10

               转换为clear_type*,显然,这正是我们所要的(谁也不能将10赋给一个函数指针)----良好的函数指针语义,不过却是泛型的。很

              巧妙,不是吗?

function的代码很简洁,骨架就这些。也许你会问,function作为一个仿函数类,怎么没有operator()?别急,function把这些恼人的东西都丢给了它的基类function1。几乎所有秘密都在那里。

不知道你有没有发现,function的骨架中也几乎没有关于模板参数的信息,除了最初的参数声明,事实上,它也将这些信息一股脑儿抛给了基类。在这过程中,混沌一团的int(int)型别也被拆开为几个模板参数传给基类:

     template<typename R,typename T0,typename Allocator>

     class function<R(T0),Allocator>   //R(T0)整个为一型别

        :public function1<R,T0,Allocator> //拆解为R,T0传给基类

好了,下面我们深入基类function1。真正丰富的宝藏在里面。

function的基类

function1的源代码像这样(与上面一样,事实上有些代码你是看不到的,为了不让你迷惑,我给出的是将宏展开后得到的代码):

     摘自:boost/function/function_template.hpp

     template<typename R,typename T0,class Allocator=std::allocator<function_base> >

     class function1:public function_base  //个个基类很简单,后面会讲解

     {  

             typedef typename detail::function::function_return_type<R>::type

                       internal_result_type;  //这其实就是typename R

                                          //但是如果Rvoid,则是个空类unusable

                                          //这是为了让invoker能够返回

          struct clear_type{}; //其作用在function的源码注释中已经讲过

      public:

          typedef T0 argument_type; //为了能和std::bind1st协同工作

          typedef T0 arg1_type;

          typedef R  result_type;   //返回类型

          typedef function1 self_type; //相当于typedef function1<R,T0,Allocator> selftype;

          //---------------end typedefs---------------------//

              function1() : function_base(),invoker(0){}//默认构造

              template<typename Functor>

          2   function1(Functor const & f,   //模板构造函数

                       typename detail::function::enable_if<   //又出现了,解释跟function源码中的一样

                            (::boost::type_traits::ice_not<

                            (is_same<Functor, int>::value)>::value),

                            int>::type = 0):

              function_base(),

              invoker(0){

          3   this->assign_to(f);   //这儿真正进行赋值,assign_to的代码在下面列出

              }

              function1(clear_type*) : function_base(), invoker(0) {} //解释见上面的function源码

              function1(const function& f) : //拷贝构造函数

              function_base(),

              invoker(0){

              this->assign_to_own(f); //专用于在function之间赋值的assignment

              }

              //-------------------end constructors------------------//

           1  result_type operator()(T0 a0) const  //使它真正成为了一个仿函数,于是你可以写

              {                                    //function<int(int)> f=g;f(10);后者调用operator()

                 if (this->empty())

                      boost::throw_exception(bad_function_call());

           2    internal_result_type result = invoker(function_base::functor,a0);

                //这是重点,function1,将任务抛给invoker,从而自己能够维持形式单纯

                //function_base::functor的型别为union any_pointer

                // union any_pointer

                // {

                //    void* obj_ptr;  //任意仿函数对象指针都可以用static_cast<>转型为void*

                //    const void* const_obj_ptr; //const仿函数准备

                //    void (*func_ptr)(); //任意函数指针都可以用reinterpret_cast<>转型为void(*)()

                //    char data[1];

                // };所以它可以通过安全转型保存所有形式的仿函数和函数指针,承载在底层保存数据的任务

                 return static_cast<result_type>(result);  //即使result_typevoid

                                                //C++标准也允许static_cast<>将任意型别向void转换

               }

           4  template<typename Functor>

               void assign_to(Functor f)

                {

                  typedef typename detail::function::get_function_tag<Functor>::type tag; //traits技巧

                  this->assign_to(f, tag());   //根据Functor的不同类型采取不同的策略。

                }                              //get_function_tag<>能萃取出Functor类型,如下:

                //struct function_ptr_tag {};    //代表函数指针类别

                //struct function_obj_tag {};    //代表仿函数对象类别

                //struct member_ptr_tag {};      //代表成员函数类别

                //struct function_obj_ref_tag {};//代表仿函数对象的reference类别

                //struct stateless_function_obj_tag {};

                //满足以下所有条件:has_trivial_constructor

                //                  has_trivial_copy

                //                  has_trivial_destructor

                //                  is_empty

                //的仿函数对象称为stateless

                //这些类型定义是为了激发函数重载,assign_to的各重载版本如下

                template<typename FunctionPtr> //如果是函数指针就调用这个版本

             5  void assign_to(FunctionPtr f, detail::function::function_ptr_tag)

                { clear();

                  if (f){

                  typedef typename detail::function::get_function_invoker1< //XXX1者都有XXX2,XXX3等版本

                                    FunctionPtr,RT0>::type invoker_type;

                  invoker = &invoker_type::invoke; //invokestatic成员函数,有多种形式,在后面将介绍它

                  function_base::manager =  //可以将它看成复制函数指针及对象,并析构函数对象的管理器

                    &detail::function::functor_manager<FunctionPtr, Allocator>::manage;

                  function_base::functor =   //交给function的函数指针或仿函数对象指针最终保存在这儿

                    function_base::manager(detail::function::make_any_pointer((void (*)())(f)),

                          detail::function::clone_functor_tag);//实际上拷贝了一份函数指针

                  }

                 }

                 ...  //其它重载版本,由于重点考虑针对函数指针的版本,所以将他们略去

                 typedef internal_result_type (*invoker_type)(detail::function::any_pointer,T0);

                 invoker_type invoker; //invoker函数指针指向某个拥有invoker_type形式的函数,

                                  //这个函数在模板化构造函数和assign系列函数中指定

             }; //end of class

好了,可能你已经被这些代码弄得头昏脑涨了。深吸一口气,我们再次沿着先前的代码作一次全程回顾,从用户代码开始:      int g(int);

     0  function<int(int)> f(g);

        f(10);

请不时回顾以前列出的代码,0代表旅程的起点,在源代码中标注出的黑色加框加底纹的数字表示初始化的过程。斜体不加框的数字表示调用f(10)所经历的行程。

0开始,显然function<int(int> f(g)唤起了function的模板构造函数1,该构造函数什么也不做,只是调用基类function1的构造函数2并传递g作为参数,function1在其构造函数中3调用assign_to函数,于是进入4,4中由get_function_tag<>萃取出函数类型特征,并激发函数重载,本例中激发的重载版本是5,在5中完成其所有初始化任务。这就是初始化的全过程。

当调用f(10);时,首先在标号1处调用operator(),然后它在内部2处将任务转交给invoker完成对真正函数的调用。

你可能迫不及待地想知道invoker是如何工作的。下面就是Boost库中的function_invoker1源代码(同样,你看实际看不到function_invoker1这样的符号,我给出的是宏展开后的代码,以便理解)

   template<typename FunctionPtr,typename Rtypename T0>

      struct function_invoker1

      {

        static R invoke(any_pointer function_ptr,T0 a0) //该函数将被赋给function1类成员invoker

        {

          FunctionPtr f = reinterpret_cast<FunctionPtr>(function_ptr.func_ptr);

          return f(a0);

        }

      };

   还记得function1的类定义的最后有这样的typedefs吗:

       typedef internal_result_type (*invoker_type)(detail::function::any_pointer,T0);

       invoker_type invoker;

   其中internal_result_typeRtypedef。并将detail::function::any_pointer(detail,function是两个namespace)简写成any_pointer,这就相当于:

       typedef R (*invoker_type)(any_pointer,T0);

这不正是function_invoker1::invoke函数的指针类型么?而在上面展示的初始化行程的第5(在源代码5)invoker成员被赋值。赋值语句像这样:

      typedef typename detail::function::get_function_invoker1<FunctionPtr,RT0>::type invoker_type;

  invoker = &invoker_type::invoke;

因为本例我给function的模板参数为int(int),而g的型别为int(*)(int)(因为5处的assign_to为模板,所以g的型别由编译器自动推导出来为int(*)(int),这在旅程的开始就已经说过),所以,get_function_invoker1<FunctionPtr,R,T0>其实被推导为get_function_invoker1<int(*)(int),int,int>get_function_invoker1内部typedeftype,像这样:

  typedef typename ct_if<(is_void<R>::value),void_function_invoker1<FunctionPtr,R,T0>,

                          function_invoker1<FunctionPtr,R,T0> >::type type;

  也就是:typedef  ct_if<(is_void<int>::value),void_function_invoker1<int(*)(int),int,int>,

                          function_invoker1<int(*)(int),int,int> >::type type;

  其中ct_if<>接受三个模板参数,当第一个bool型模板参数为true时其内部的type被定义为第二个参数型别,如果为false则定义为第三个参数型别,像cond?t:f三元表达式,不过ct_if<>是在编译期。其完整定义见boost/pending/ct_if.hpp,而is_void<>可想而知的是,如果传voidvalue就是true,否则为false。所以,以上的typedef被推导为:

         typedef  function_invoker1<int(*)(int),int,int> type;

  所以get_function_invoker1<int(*)(int),int,int>::type其实是function_invoker1<int(*)(int),int,int>,这也正是invoker_type

  所以赋值语句invoker=&invoker_type::invoke最终就是:

         invoker=&function_invoker1<int(*)(int),int,int>::invoke;

  function_invoker1<int(*)(int),int,int>::invoke静态成员函数也被具现化为:

        static int invoke(any_pointer function_ptr,int a0){

          int (*f)(int) = reinterpret_cast<int(*)(int)>(function_ptr.func_ptr); //转型为g的型别

          return f(a0);   //调用

        }

  这正是我们想要的,不是吗?

  现在看一看调用f(10);时的情况:将目光转向2处,invoker(function_base::functor,a0);function_base::functor保存着转型为void(*)()型的函数指针g,而invoker已经是上面的静态成员函数的指针,所以也就是调用它,目光往上移四行,就是那个函数,一切都很完美。

  ~,总算完成了,但是这只是一种情况,如果接受的是仿函数,则invoker将有function_obj_invoker1与它对应,后者也是一个类似function_invoker1的模板,它的invoke静态成员函数的形式也是:

      static R invoke(any_pointer function_obj_ptr,T0 a0);

只不过function_obj_ptr是指向仿函数对象的指针,于是它可以这样调用:

      FunctionObj* f = (FunctionObj*)(function_obj_ptr.obj_ptr);

      return (*f)(a0); //将责任最终交给仿函数对象的operator()

而如果接受的是成员函数呢,Boost并没有为成员函数准备一份member_function_ptr,原因很简单,function使用了额外的步骤:先将成员函数封装为仿函数,再转向仿函数的assign_to版本。所以成员函数其实也使用function_obj_invoker1。后面会详细介绍该过程。

使用invoker的额外好处:

     由于function的构造和赋值函数以及其基类的构造赋值函数都是模板函数,那是为了能够接受形式兼容的仿函数。这在文章的开头将Boost::function与普通函数指针作比较时有所说明。但问题是:既然是模板函数,那么就能够接受所有的函数和仿函数,并且前面已经讲过,function的基类function1会不分青红皂白将凡是函数的转型为void(*)()型,将凡是仿函数的不分青红皂白将其指针转型为void*型,这是为了以统一的形式保存,既然这样,以下代码:

     int gg(int,int);          //注意gg的形式

     function<int(int)> f(gg); //以及具现化function的函数类型

     gg(10,10);

是不是能够通过编译呢?显然从语意和安全上讲都应该不能。幸运的是,它确实不能,甚至即使将第三行gg(10,10)去掉也仍然不能,然而到底是哪个环节让它惹恼了编译器呢?答案就在invoker身上。

      考虑上面的例子,请将目光移向5处的assign_to模板函数,你知道,function的模板化构造函数最终是要调用它的(这在前面的全程回顾中已经讲过),看看它的模板参数FunctionPtr,被编译器决议如下:

      typename FunctionPtr=int (*)(int,int) //因为传给构造函数的为gg,而gg的型别正是如此

再看看它的第三行的那个typedef:

      typedef typename detail::function::get_function_invoker1<

                                    FunctionPtr,RT0>::type invoker_type;

这里的R,T0是在具现化function模板时决议出的,你写的是function<int(int)>,于是RintT0int。所以在本例中,上面的typedef被推导为:

      typedef  function_invoker1<int(*)(int,int),int,int> invoker_type;

    现在请回顾上面列出的function_invoker1源代码,其内部唯一的静态成员函数invoke

        static R invoke(any_pointer function_ptr,T0 a0)

        {

          FunctionPtr f = reinterpret_cast<FunctionPtr>(function_ptr.func_ptr);

          return f(a0);

        }

    被具现化为(请注意 typename FunctionPtr=int (*)(int,int) )

       static int invoke(any_pointer function_ptr,int a0)

       {

         int (*f)(int,int)=reinterpret_cast<int(*)(int,int)>(function_ptr.func_ptr);

         return f(a0); //错啦!瞧瞧f的型别,f接受一个参数吗?编译器在此打住。

       }

    很精妙不是吗?FunctionPtr这个型别信息其实是由function_invoker1的具现化过程传入的。

function如何对待成员函数:

    请转到4,其中get_function_tag<>会忠实地将成员函数特征萃取为struct member_ptr_tag;于是转向为成员函数专设的重载版本。该版本只有一行:

         this->assign_to(mem_fn(f));

    mem_fun(f)返回一个封装成员函数指针f的仿函数,于是控制流再次流向assign_to的仿函数版本,于是一切都优雅的解决了。软件工程有个基本原理:几乎所有问题都可以通过增加一个或几个中间层来解决,而对成员函数的封装正是这样的中间层。

成员函数封装的细节:

首先是如何封装,Boost提供了一系列的类来封装成员函数,例如,为R (T::*)(T0)形式提供封装的类是inner_mf1(这同样是将宏展开后的结果,事实上你看不到这个,你能看到的是BOOST_MEM_FN_NAME(mf1)这样的类名,而BOOST_MEM_FN_NAME宏被定义为:#define BOOST_MEM_FN_NAME(X) inner_##X;下面是inner_mf1类的源码:

摘自boost/bind/mem_fn_template.hpp并且宏已被展开:

template<class R,class T,class A1> class inner_mf1

{

public:

    typedef R result_type;

    typedef T * first_argument_type; //for bind1st,bind2nd

    typedef A1 second_argument_type; //as above

private:

    typedef R (T::*F) (A1);

    F f_;

    ...

public:

    explicit inner_mf1(F f): f_(f) {}

    R operator()(T * p, A1 a1) const{

        return (p->*f_)(a1)

}

...

  };

 Boost库并同时提供有相应的mem_fn函数,看到上面的类名inner_mf1了吗?inner_表示内部使用,你应该调用mem_fn来获得而非直接具现化一个inner_mf1mem_fn源码如下(摘自:boost/bind/mem_fn_cc.hpp宏已扩展,_mfi为一namespace)

template<class R, class T, class A1> _mfi::inner_mf1<R, T, A1> mem_fn(R (T::*f) (A1)){

    return _mfi::inner_mf1<R, T, A1>(f);

}

需要注意的是,如果你先前这样调用成员函数:someObj.someMF(10);经过封装后你需要这样调用:

    MF_Functor(&someObj,10);//第一个参数对应于this指针

所以如果你有一个类class X{public: int XMF(int){} };而你想要一个代表它的唯一的成员函数XMFfunction对象,你需要这样写:

   function<int(X*,int)> xf(mem_fn(X::XMF));

并可以且应该这样调用:

   X x;

   int result=xf(&x,10); //传某个X对象的指针以当作this指针

safe_bool惯用手法

如你所知,对于函数指针fptr可以这样测试它if(fptr) ...,所以function也应该提供这一特性,然而如果直接提供operator bool()则会导致这样的代码:function<int(int)> f;bool b=f;通过编译,这显然不妥,所以function用另一个巧妙的手法替代它,既所谓safe_bool惯用手法,这在function定义内部的源码如下:

struct dummy { void nonnull(){};};

typedef void (dummy::*safe_bool)(); //不将safe_bool定义为int型可以防止将f赋给int型变量

public:

 operator safe_bool () const 

 { return (this->empty())? 0 : &dummy::nonnull; }

 bool operator!() const

 { return this->empty(); }

这样你就可以写if(safe_bool(f))...来测试f是否为NULL。并且你再也不可能写出有歧义如bool b=f;int i=f;的语句了。

get_function_tag<>

关于get_function_tag<>,它运用了traits技巧,这与本文关系不是很大,但为了不让你迷惑或者满足你的好奇心,略提一二。对于int g(int); 有一个极其重要的地方就是:g是指针型别,虽然这很显然,但是考虑这样的代码:

template<typename T> struct is_pointer{enum{value=0};};

template<typename T> struct is_pointer<T*>{enum{value=1};};

std::cout<<is_pointer<int(*)(int)>::value; //这将输出 1

也就是说int(*)(int)可以与T*形式匹配,匹配时Tint(int)。在get_function_tag<>内部正是由这种技巧判断所接受的是否是函数指针,如果不是指针,则认为是仿函数对象。

最后一些细节

1. 我没有给出function_base的源代码,实际上那很简单,它最主要的成员就是union any_pointer型的数据成员

           detail::function::any_pointer functor;  //用于统一保存函数指针及仿函数对象指针

2. 我没有给出functor_manager的信息,实际上它与function的实现没有太大关系,它负责copydelete函数对象,如果必要的话。所以我将它略去,它的源码在:boost/function/function_base.hpp里。

3. 我给出的源代码是将宏展开后的版本,实际的代码中充斥着让人眼花缭乱的宏,关于那些宏则又是一个奇妙的世界。Boost库通过那些宏省去了许多可见代码量。随着函数参数的不同,那些宏会扩展function2,function3...各个版本。

本文只研究了int(int)型的情况,其它只是参数数目的改变而已。经过宏的扩展,function的偏特化版本将有:

  template<typename R,typename Allocator>

  class function<R(),Allocator>:public function0<R,Allocator>

  {...};

  template<typename R,typename T0,typename Allocator>

  class function<R(T0),Allocator>:public function1<R,T0,Allocator>

  {...};

  template<typename R,typename T0,typename T1,typename Allocator>

  class function<R(T0,T1),Allocator>:public function2<R,T0,T1,Allocator>

  {...};

  等更多版本,一共有BOOST_FUNCTION_MAX_ARGS+1个版本,BOOST_FUNCTION_MAX_ARGS为一个宏,表示最多能够接受有多少个参数的函数及仿函数对象。其中的function0,function1,function2也由宏扩展出。




相关文章

相关软件