• std::bind技术内幕


    引子

      最近群里比较热闹,大家都在山寨c++11的std::bind,三位童孩分别实现了自己的bind,代码分别在这里:

      这些实现思路和ms stl的std::bind的实现思路是差不多的,只是在实现的细节上有些不同。个人觉得木头云的实现更简洁,本文中的简单实现中select函数用的是木头云的,在此表示感谢。下面我们来分析一下bind的基本原理。

    bind的基本原理

      bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数、函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f = bind(&func, _1, _2);,调用的时候通过f(1,2)实现调用。关于bind的用法更多的介绍可以参考我博客中介绍

      要实现一个bind需要解决两个问题,第一个是保存可调用对象及其形参,第二个是如何实现调用。下面来分析如何解决这两个问题。

    保存可调用对象

      实现bind的首先要解决的问题是如何将可调用对象保存起来,以便在后面调用。要保存可调用对象,需要保存两个东西,一个是可调用对象的实例,另一个是可调用对象的形参。保存可调用对象的实例相很简单,因为bind时直接要传这个可调用对象的,将其作为一个成员变量即可。而保存可调用对象的形参就麻烦一点,因为这个形参是变参,不能直接将变参作为成员变量。如果要保存变参的话,我们需要用tuple来将变参保存起来。

    可调用对象的执行

      bind的形参因为是变参,可以是0个,也可能是多个,大部分情况下是占位符,还有可能占位符和实参都有。正是由于bind绑定的灵活性,导致我们不得不在调用的时候需要找出哪些是占位符,哪些是实参。如果某个一参数是实参我们就不处理,如果是占位符,我们就要将这个占位符替换为对应的实参。比如我们绑定了一个三元函数:auto f = bind(&func, _1, 2, _2);调用时f(1,3);由于绑定时有三个参数,一个实参,两个占位符,调用时传入了两个实参,这时我们就要将占位符_1替换为实参1,占位符_2替换为实参3。这个占位符的替换需要按照调用实参的顺序来替换,如果调用时的实参个数比占位符要多,则忽略多余的实参。
      调用的实参,我们也会先将其转换为tuple,用于在后面去替换占位符时,选取合适的实参。

    bind实现的关键技术

    将tuple展开为变参

      前面讲到绑定可调用对象时,将可调用对象的形参(可能含占位符)保存起来,保存到tuple中了。到了调用阶段,我们就要反过来将tuple展开为可变参数,因为这个可变参数才是可调用对象的形参,否则就无法实现调用了。这里我们会借助于一个整形序列来将tuple变为可变参数,在展开tuple的过程中我们还需要根据占位符来选择合适实参,即占位符要替换为调用实参。

    根据占位符来选择合适的实参

      这个地方比较关键,因为tuple中可能含有占位符,我们展开tuple时,如果发现某个元素类型为占位符,则从调用的实参生成的tuple中取出一个实参,用来作为变参的一个参数;当某个类型不为占位符时,则直接从绑定时生成的形参tuple中取出参数,用来作为变参的一个参数。最终tuple被展开为一个变参列表,这时,这个列表中没有占位符了,全是实参,就可以实现调用了。这里还有一个细节要注意,替换占位符的时候,如何从tuple中选择合适的参数呢,因为替换的时候要根据顺序来选择。这里是通过占位符的模板参数I来选择,因为占位符place_holder<I>的实例_1实际上place_holder<1>, 占位符实例_2实际上是palce_holder<2>,我们是可以根据占位符的模板参数来获取其顺序的。

    bind的简单实现

    #include <tuple>
    #include <type_traits>
    using namespace std;
    
    template<int...>
    struct IndexTuple{};
    
    template<int N, int... Indexes>
    struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{};
    
    template<int... indexes>
    struct MakeIndexes<0, indexes...>
    {
        typedef IndexTuple<indexes...> type;
    };
    
    template <int I>
    struct Placeholder
    {
    };
    
    Placeholder<1> _1; Placeholder<2> _2; Placeholder<3> _3; Placeholder<4> _4; Placeholder<5> 
    
    _5; Placeholder<6> _6; Placeholder<7> _7;
    Placeholder<8> _8; Placeholder<9> _9; Placeholder<10> _10;
    
    // result type traits
    
    template <typename F>
    struct result_traits : result_traits<decltype(&F::operator())> {};
    
    template <typename T>
    struct result_traits<T*> : result_traits<T> {};
    
    /* check function */
    
    template <typename R, typename... P>
    struct result_traits<R(*)(P...)> { typedef R type; };
    
    /* check member function */
    template <typename R, typename C, typename... P> 
    struct result_traits<R(C::*)(P...)> { typedef R type; };
    
    template <typename T, class Tuple>
    inline auto select(T&& val, Tuple&)->T&&
    {
        return std::forward<T>(val);
    }
    
    template <int I, class Tuple>
    inline auto select(Placeholder<I>&, Tuple& tp) -> decltype(std::get<I - 1>(tp))
    {
        return std::get<I - 1>(tp);
    }
    
    // The invoker for call a callable
    template <typename T>
    struct is_pointer_noref
        : std::is_pointer<typename std::remove_reference<T>::type>
    {};
    
    template <typename T>
    struct is_memfunc_noref
        : std::is_member_function_pointer<typename std::remove_reference<T>::type>
    {};
    
    template <typename R, typename F, typename... P>
    inline typename std::enable_if<is_pointer_noref<F>::value,
    R>::type invoke(F&& f, P&&... par)
    {
        return (*std::forward<F>(f))(std::forward<P>(par)...);
    }
    
    template <typename R, typename F, typename P1, typename... P>
    inline typename std::enable_if<is_memfunc_noref<F>::value && is_pointer_noref<P1>::value,
    R>::type invoke(F&& f, P1&& this_ptr, P&&... par)
    {
        return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(par)...);
    }
    
    template <typename R, typename F, typename P1, typename... P>
    inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value,
    R>::type invoke(F&& f, P1&& this_obj, P&&... par)
    {
        return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(par)...);
    }
    
    template <typename R, typename F, typename... P>
    inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value,
    R>::type invoke(F&& f, P&&... par)
    {
        return std::forward<F>(f)(std::forward<P>(par)...);
    }
    
    template<typename Fun, typename... Args>
    struct Bind_t
    {
        typedef typename decay<Fun>::type FunType;
        typedef std::tuple<typename decay<Args>::type...> ArgType;
    
        typedef typename result_traits<FunType>::type     result_type;
    public:
        template<class F, class... BArgs>
        Bind_t(F& f,  BArgs&... args) : m_func(f), m_args(args...)
        {    
    
        }
    
        template<typename F, typename... BArgs>
        Bind_t(F&& f, BArgs&&... par) : m_func(std::move(f)), m_args(std::move(par)...)
        {}
    
        template <typename... CArgs>
        result_type operator()(CArgs&&... args)
        {
            return do_call(MakeIndexes<std::tuple_size<ArgType>::value>::type(), 
    
    std::forward_as_tuple(std::forward<CArgs>(args)...));
        }
    
        template<typename ArgTuple, int... Indexes >
        result_type do_call(IndexTuple< Indexes... >& in, ArgTuple& argtp)
        {
            return simple::invoke<result_type>(m_func, select(std::get<Indexes>(m_args), 
    
    argtp)...);
            //return m_func(select(std::get<Indexes>(m_args), argtp)...);
        }
    
    private:
        FunType m_func;
        ArgType m_args;
    };
    
    template <typename F, typename... P>
    inline Bind_t<F, P...> Bind(F&& f, P&&... par)
    {
        return Bind_t<F, P...>(std::forward<F>(f), std::forward<P>(par)...);
    }
    
    template <typename F, typename... P>
    inline Bind_t<F, P...> Bind(F& f, P&... par)
    {
        return Bind_t<F, P...>(f, par...);
    }
    View Code

    测试代码:

    void TestFun1(int a, int b, int c)
    {
    }
    
    void TestBind1()
    {
        Bind(&TestFun1,  _1,  _2,  _3)(1, 2, 3);
        Bind(&TestFun1, 4, 5, _1)(6);
        Bind(&TestFun1, _1, 4, 5)(3);
        Bind(&TestFun1, 3, _1,  5)(4);
    }
    View Code

    bind更多的实现细节

      由于只是展示bind实现的关键技术,很多的实现细节并没有处理,比如参数是否是引用、右值、const volotile、绑定非静态的成员变量都还没处理,仅仅供学习之用,并非是重复发明轮子,只是展示bind是如何实现, 实际项目中还是使用c++11的std::bind为好。null同学还图文并茂的介绍了bind的过程,有兴趣的童孩可以看看.

    关于bind的使用

      在实际使用过程中,我更喜欢使用lambda表达式,因为lambda表达式使用起来更简单直观,lambda表达式在绝大多数情况下可以替代bind。

    如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。

    c++11 boost技术交流群:296561497,欢迎大家来交流技术。

  • 相关阅读:
    BZOJ3197:[SDOI2013]刺客信条——题解
    C 程序与 C++ 程序之间的相互调用
    使用Dev C++调试(debug)程序
    ARM 汇编指令 ADR 与 LDR 使用
    华为交换机以 LACP 模式实现链路聚合
    DLCI 简介
    华为路由器帧中继 FR 实验
    GVRP 的工作机制和工作模式
    华为路由器 HDLC 实验
    华为路由器 IPSec 与 GRE 结合实验
  • 原文地址:https://www.cnblogs.com/qicosmos/p/3723388.html
Copyright © 2020-2023  润新知