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,欢迎大家来交流技术。

posted on 2014-05-12 14:45  qicosmos(江南)  阅读(13791)  评论(1编辑  收藏  举报

导航