zoukankan      html  css  js  c++  java
  • 在变参模版出现之前,functional如何实现bind功能

    一、std::tr1::bind及std::tr1::function函数的意义
    在第一次见到std库中bind函数的时候,有一种你们城里人真会玩的感觉,把模版用的出神入化。但是,更关键的是这么华丽的用法,是为了解决什么问题呢?这个问题本身可能比它们如何实现更加重要。其实科技的发展也是大抵如此,往往新技术的发展是为了解决什么问题,这个通常教科书中都不会告诉大家。就像之前学习微积分一样,看了很多推倒,证明了很多公式,但是这些东西到底是为了解决什么问题,在什么场景下触发了它们的出现,教科书从来没有说。
    function和bind函数的出现更多的是一种“适配”功能。比方说,框架层定义了一个回调函数,函数有确定的类型和返回值。如果业务希望在回调中实现自己的功能,就需要定义相同类型的函数实现即可。但是这个是理想情况下的情况,而在真正的现实场景中,情况往往会更加复杂。举个栗子:假设一棵树结构提供了一个这棵树的遍历接口,回调函数的参数是树中的一个节点。但是现在有一个功能,需要根据用户的数据,为树中的每个节点执行一个 “加上x”的功能。此时理想情况下,我们希望给回调函数增加一个额外的参数用于表示该参数值x。
    具体代码如下:
    tsecer@harry: cat -n tr1.bind.demo.cpp 
         1 #include <map>
         2 using namespace std;
         3 typedef void (*mapVisitor)(int&);
         4 int traverseMap(mapVisitor visitor)
         5 {
         6 static map<int,int> mapDemo;
         7 for (map<int,int>::iterator iter = mapDemo.begin(); iter != mapDemo.end(); iter++)
         8 {
         9 visitor(iter->second);
        10 }
        11 }
        12
        13 void nodeAdder(int &node, int val)
        14 {
        15 node += val;
        16 }
        17
    tsecer@harry: g++ -c tr1.bind.demo.cpp 
    tsecer@harry: 
     
    可以看到,nodeAdder函数由于参数个数为两个,所以无法直接传递给traverseMap使用,此时functional的作用就可以体现出来了。
    tsecer@harry: cat function.cpp 
    #include <map>
    #include <tr1/functional>
     
    using namespace std;
    typedef tr1::function<void(int &)> mapVisitor;
     
    int traverseMap(mapVisitor visitor)
    {
    static map<int,int> mapDemo;
    for (map<int,int>::iterator iter = mapDemo.begin(); iter != mapDemo.end(); iter++)
    {
    visitor(iter->second);
    }
    }
     
    void nodeAdder(int &node, int val)
    {
    node += val;
    }
     
    int main(int argc, char * argv[])
    {
    traverseMap(tr1::bind(nodeAdder, tr1::placeholders::_1, argc));
    }
     
    tsecer@harry: g++ -c function.cpp
    tsecer@harry: 
    可以看到,虽然回调函数中定义的类型是void(int &),使用时依然可以将void nodeAdder(int &node, int val)类型的函数传递进去。所以简言之,functional和bind的作用就是可以将不同数量的函数之间进行适配。那么大家可能会有人问了,这么神奇的功能是怎么实现的呢?下面结合gcc 4.1.0版本的实现来讨论下这个问题(由于新的gcc版本使用了变参模版,所以看起来不是很直观)。
    二、实现代码的位置
    gcc的相关实现代码主要位于gcc-4.1.0libstdc++-v3include r1文件夹下,从实现上看,其中有大量的宏操作来进行文件的重复包含和展开,例如
    gcc-4.1.0libstdc++-v3include r1functional_iterate.h
     
    template<typename _Functor _GLIBCXX_COMMA _GLIBCXX_TEMPLATE_PARAMS>
    class _Bind<_Functor(_GLIBCXX_TEMPLATE_ARGS)>
      : public _Weak_result_type<_Functor>
    {
      typedef _Bind __self_type;
     
      _Functor _M_f;
      _GLIBCXX_BIND_MEMBERS
     
     public:
    #if _GLIBCXX_NUM_ARGS == 0
      explicit
    #endif
      _Bind(_Functor __f _GLIBCXX_COMMA _GLIBCXX_PARAMS)
        : _M_f(__f) _GLIBCXX_COMMA _GLIBCXX_BIND_MEMBERS_INIT { }
     
    #define _GLIBCXX_BIND_REPEAT_HEADER <tr1/bind_iterate.h>
    #include <tr1/bind_repeat.h>
    #undef _GLIBCXX_BIND_REPEAT_HEADER
    };
     
    进一步在gcc-4.1.0libstdc++-v3include r1ind_repeat.h文件中,其中包含了大量这样的代码,也就是不断的定义不同的宏,然后重复包含一个文件,从而将文件循环展开。这个过程本身并没有什么技巧在里面,只是逐层的展开而已,但是也正是这些看似笨重的宏支撑着看起来非常神奇的functional功能。
     
    #define _GLIBCXX_BIND_NUM_ARGS 4
    #define _GLIBCXX_BIND_COMMA ,
    #define _GLIBCXX_BIND_TEMPLATE_PARAMS typename _U1, typename _U2, typename _U3, typename _U4
    #define _GLIBCXX_BIND_TEMPLATE_ARGS _U1, _U2, _U3, _U4
    #define _GLIBCXX_BIND_PARAMS _U1& __u1, _U2& __u2, _U3& __u3, _U4& __u4
    #define _GLIBCXX_BIND_ARGS __u1, __u2, __u3, __u4
    #include _GLIBCXX_BIND_REPEAT_HEADER
    #undef _GLIBCXX_BIND_ARGS
    #undef _GLIBCXX_BIND_PARAMS
    #undef _GLIBCXX_BIND_TEMPLATE_ARGS
    #undef _GLIBCXX_BIND_TEMPLATE_PARAMS
    #undef _GLIBCXX_BIND_COMMA
    #undef _GLIBCXX_BIND_NUM_ARGS
     
    #define _GLIBCXX_BIND_NUM_ARGS 5
    #define _GLIBCXX_BIND_COMMA ,
    #define _GLIBCXX_BIND_TEMPLATE_PARAMS typename _U1, typename _U2, typename _U3, typename _U4, typename _U5
    #define _GLIBCXX_BIND_TEMPLATE_ARGS _U1, _U2, _U3, _U4, _U5
    #define _GLIBCXX_BIND_PARAMS _U1& __u1, _U2& __u2, _U3& __u3, _U4& __u4, _U5& __u5
    #define _GLIBCXX_BIND_ARGS __u1, __u2, __u3, __u4, __u5
    #include _GLIBCXX_BIND_REPEAT_HEADER
    #undef _GLIBCXX_BIND_ARGS
    #undef _GLIBCXX_BIND_PARAMS
    #undef _GLIBCXX_BIND_TEMPLATE_ARGS
    #undef _GLIBCXX_BIND_TEMPLATE_PARAMS
    #undef _GLIBCXX_BIND_COMMA
    #undef _GLIBCXX_BIND_NUM_ARGS
     
    三、大致的思路
    gcc在实现该功能的时候实现的是技巧性的穷举。作为一个适配功能,假设函数最多有10个参数,那么在适配的时候只需要为每个调用类型适配所有的实现类型即可,加上无参数类型,总共11 * 11 = 121 种组合而已,如果真是有人把这些所有的组合都枚举写出来,那的确会贻笑大方,所以gcc的实现是通过宏来进行枚举。
     
    gcc-4.1.0libstdc++-v3include r1functional_iterate.h这个文件被循环包含11次,然后该文件中的<tr1/bind_iterate.h>也被循环包含11次,从而完成这11 * 11的枚举
    template<typename _Functor _GLIBCXX_COMMA _GLIBCXX_TEMPLATE_PARAMS>
    class _Bind<_Functor(_GLIBCXX_TEMPLATE_ARGS)>
      : public _Weak_result_type<_Functor>
    {
      typedef _Bind __self_type;
     
      _Functor _M_f;
      _GLIBCXX_BIND_MEMBERS
     
     public:
    #if _GLIBCXX_NUM_ARGS == 0
      explicit
    #endif
      _Bind(_Functor __f _GLIBCXX_COMMA _GLIBCXX_PARAMS)
        : _M_f(__f) _GLIBCXX_COMMA _GLIBCXX_BIND_MEMBERS_INIT { }
     
    #define _GLIBCXX_BIND_REPEAT_HEADER <tr1/bind_iterate.h>
    #include <tr1/bind_repeat.h>
    #undef _GLIBCXX_BIND_REPEAT_HEADER
    };
    看下<tr1/bind_iterate.h>中的内容
    operator()(_GLIBCXX_BIND_PARAMS) const
    { return _M_f(_GLIBCXX_BIND_V_ARGS); }
     
    #if _GLIBCXX_BIND_NUM_ARGS > 0
    template<_GLIBCXX_BIND_TEMPLATE_PARAMS>
    #endif
    #ifdef _GLIBCXX_BIND_HAS_RESULT_TYPE
    result_type
    #else
    typename result_of<volatile _Functor(_GLIBCXX_BIND_V_TEMPLATE_ARGS(volatile))>::type
    #endif
    operator()(_GLIBCXX_BIND_PARAMS) volatile
    { return _M_f(_GLIBCXX_BIND_V_ARGS); }
     
    #if _GLIBCXX_BIND_NUM_ARGS > 0
    template<_GLIBCXX_BIND_TEMPLATE_PARAMS>
    #endif
    #ifdef _GLIBCXX_BIND_HAS_RESULT_TYPE
    result_type
    #else
    typename result_of<const volatile _Functor(_GLIBCXX_BIND_V_TEMPLATE_ARGS(const volatile))>::type
    #endif
    operator()(_GLIBCXX_BIND_PARAMS) const volatile
    { return _M_f(_GLIBCXX_BIND_V_ARGS); }
     
    四、使用gcc的预处理验证下这个结果
    方法是编译一个gcc4.1.0版本的可执行文件,然后查看包含之后预处理的输出结果
    tsecer@harry: pwd
    /home/tsecer/Downloads/gccobj-4.1.0/gcc
    tsecer@harry: cat /home/tsecer/CodeTest/tr1.bind/tr1.bind.cpp 
    #include "tr1/functional"
    int main()
    {
    return 0;
    }
    tsecer@harry: ./cc1plus -E -I  ../i686-pc-linux-gnu/libstdc++-v3/include/  /home/tsecer/CodeTest/tr1.bind/tr1.bind.cpp  
    从这个预处理之后的内容可以看到,其中对于5个参数的调用,定义了参数个数为1、2、3、4、等所有可能的函数实现,所以这个实现是一个完全的组合枚举实现。
    ……
     
    operator()(_U1& __u1, _U2& __u2, _U3& __u3, _U4& __u4, _U5& __u5) const
    { return _M_f(_Mu<_T1>()(_M_arg1, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)), _Mu<_T2>()(_M_arg2, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5))); }
    ……
     
    operator()(_U1& __u1, _U2& __u2, _U3& __u3, _U4& __u4, _U5& __u5) const
    { return _M_f(_Mu<_T1>()(_M_arg1, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)), _Mu<_T2>()(_M_arg2, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)), _Mu<_T3>()(_M_arg3, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5))); }
    ……
     
    operator()(_U1& __u1, _U2& __u2, _U3& __u3, _U4& __u4, _U5& __u5) const
    { return _M_f(_Mu<_T1>()(_M_arg1, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)), _Mu<_T2>()(_M_arg2, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)), _Mu<_T3>()(_M_arg3, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)), _Mu<_T4>()(_M_arg4, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5))); }
     
    五、placeholder的实现
    可以看到,在调用具体实现的时候,参数通过
    _M_f(_Mu<_T1>()(_M_arg1, ::std::tr1::tie(__u1, __u2, __u3, __u4, __u5)),
    形式提供,其中的关键就是在于_Mu模版函数的多态实现,根据提供的第一个参数的类型,推导出后面两个参数是编译时常量true或者false,进而对第二个、第三个参数为true/false的类型进行组合特例化,从所有参数tie获得的tuple中选择特定的参数。
     
    gcc-4.1.0libstdc++-v3include r1functional
     
     
      /**
       *  @if maint
       *  Maps an argument to bind() into an actual argument to the bound
       *  function object [TR1 3.6.3/5]. Only the first parameter should
       *  be specified: the rest are used to determine among the various
       *  implementations. Note that, although this class is a function
       *  object, isn't not entirely normal because it takes only two
       *  parameters regardless of the number of parameters passed to the
       *  bind expression. The first parameter is the bound argument and
       *  the second parameter is a tuple containing references to the
       *  rest of the arguments.
       *  @endif
       */
      template<typename _Arg,
               bool _IsBindExp = is_bind_expression<_Arg>::value,
               bool _IsPlaceholder = (is_placeholder<_Arg>::value > 0)>
        class _Mu;
     
    ……
      /**
       *  @if maint
       *  If the argument is a placeholder for the Nth argument, returns
       *  a reference to the Nth argument to the bind function object.
       *  [TR1 3.6.3/5 bullet 3]
       *  @endif
       */
      template<typename _Arg>
        class _Mu<_Arg, false, true>
        {
        public:
          template<typename _Signature> class result;
     
          template<typename _CVMu, typename _CVArg, typename _Tuple>
          class result<_CVMu(_CVArg, _Tuple)>
          {
            // Add a reference, if it hasn't already been done for us.
            // This allows us to be a little bit sloppy in constructing
            // the tuple that we pass to result_of<...>.
            typedef typename tuple_element<(is_placeholder<_Arg>::value - 1),
                                           _Tuple>::type __base_type;
     
          public:
            typedef typename add_reference<__base_type>::type type;
          };
     
          template<typename _Tuple>
          typename result<_Mu(_Arg, _Tuple)>::type
          operator()(const volatile _Arg&, const _Tuple& __tuple) const volatile
          {
            return ::std::tr1::get<(is_placeholder<_Arg>::value - 1)>(__tuple);
          }
        }
     
    gcc-4.1.0libstdc++-v3include r1functional_iterate.h
    placeholders的定义
     
    namespace placeholders
    {
    namespace
    {
       _Placeholder<_GLIBCXX_NUM_ARGS> _GLIBCXX_JOIN(_,_GLIBCXX_NUM_ARGS);
    }
    }
     
    is_bind_expression则是判断是否是定义于_Bind类中的成员。
  • 相关阅读:
    【常用配置】Spring框架web.xml通用配置
    3.从AbstractQueuedSynchronizer(AQS)说起(2)——共享模式的锁获取与释放
    2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放
    1.有关线程、并发的基本概念
    0.Java并发包系列开篇
    SpringMVC——DispatcherServlet的IoC容器(Web应用的IoC容器的子容器)创建过程
    关于String的问题
    Spring——Web应用中的IoC容器创建(WebApplicationContext根应用上下文的创建过程)
    <<、>>、>>>移位操作
    System.arraycopy(src, srcPos, dest, destPos, length) 与 Arrays.copyOf(original, newLength)区别
  • 原文地址:https://www.cnblogs.com/tsecer/p/10488046.html
Copyright © 2011-2022 走看看