在变参模版出现之前,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.0\libstdc++-v3\include\tr1文件夹下,从实现上看,其中有大量的宏操作来进行文件的重复包含和展开,例如
gcc-4.1.0\libstdc++-v3\include\tr1\functional_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.0\libstdc++-v3\include\tr1\bind_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.0\libstdc++-v3\include\tr1\functional_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.0\libstdc++-v3\include\tr1\functional
 
 
  /**
   *  @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.0\libstdc++-v3\include\tr1\functional_iterate.h
placeholders的定义
 
namespace placeholders
{
namespace
{
   _Placeholder<_GLIBCXX_NUM_ARGS> _GLIBCXX_JOIN(_,_GLIBCXX_NUM_ARGS);
}
}
 
is_bind_expression则是判断是否是定义于_Bind类中的成员。

posted on 2019-03-07 10:39  tsecer  阅读(158)  评论(0编辑  收藏  举报

导航