引子

  最近群里比较热闹,大家都在山寨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 - , N - , Indexes...>{}; template<int... indexes>
struct MakeIndexes<, indexes...>
{
typedef IndexTuple<indexes...> type;
}; template <int I>
struct Placeholder
{
}; Placeholder<> _1; Placeholder<> _2; Placeholder<> _3; Placeholder<> _4; Placeholder<> _5; Placeholder<> _6; Placeholder<> _7;
Placeholder<> _8; Placeholder<> _9; Placeholder<> _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 - >(tp))
{
return std::get<I - >(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...);
}

测试代码:

void TestFun1(int a, int b, int c)
{
} void TestBind1()
{
Bind(&TestFun1, _1, _2, _3)(, , );
Bind(&TestFun1, , , _1)();
Bind(&TestFun1, _1, , )();
Bind(&TestFun1, , _1, )();
}

bind更多的实现细节

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

关于bind的使用

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

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

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

std::bind技术内幕的更多相关文章

  1. 关于std::bind的文章收集

    C++11 FAQ中文版:std::function 和 std::bind 2011-03-02 16:25 by 陈良乔 常规性地介绍了function和bind的使用,还不会用的同学可以看看 b ...

  2. 【转】COM技术内幕(笔记)

    COM技术内幕(笔记) COM--到底是什么?--COM标准的要点介绍,它被设计用来解决什么问题?基本元素的定义--COM术语以及这些术语的含义.使用和处理COM对象--如何创建.使用和销毁COM对象 ...

  3. 深入理解linux网络技术内幕读书笔记(三)--用户空间与内核的接口

    Table of Contents 1 概论 1.1 procfs (/proc 文件系统) 1.1.1 编程接口 1.2 sysctl (/proc/sys目录) 1.2.1 编程接口 1.3 sy ...

  4. 纠错《COM技术内幕》之ProgID

    近期在看<COM技术内幕>,看到第六章时发现该章节在解释ProgID时有点错误,特此记录一下,也给正在学习COM的小伙伴们一个提示. 并且我发现该问题存在于一些非常多大型软件的COM组件中 ...

  5. zzTensorflow技术内幕:

    性能优势 TensorFlow在大规模分布式系统上的并行效率相当高,如下图所示: 图5:TensorFlow并发效率 在GPU数量小于16时,基本没有性能损耗,在50块的时候,可以获得80%的效率,也 ...

  6. 第19课 lambda vs std::bind

    一. std::bind (一)std::bind实现的关键技术 [编程实验]探索bind原理,实现自己的bind函数 #include <iostream> #include <t ...

  7. 简读《ASP.NET Core技术内幕与项目实战》之3:配置

    特别说明:1.本系列内容主要基于杨中科老师的书籍<ASP.NET Core技术内幕与项目实战>及配套的B站视频视频教程,同时会增加极少部分的小知识点2.本系列教程主要目的是提炼知识点,追求 ...

  8. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  9. 《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(下)

    索引: 一.SQL Server的体系结构 二.查询 三.表表达式 四.集合运算 五.透视.逆透视及分组 六.数据修改 七.事务和并发 八.可编程对象 五.透视.逆透视及分组 5.1 透视 所谓透视( ...

随机推荐

  1. Makefile 和 CMakeLists.txt

    Makefile Makefile 的格式 target: prerequisites [tab]command 例子 #Makefile all:chap1 chap2 chap1: - - - : ...

  2. Log4j 把不同包的日志打印到不同位置

    如果需要将不同的日志打印到不同的地方,则需要定义不同的Appender,然后定义每一个 Appender的日志级别.打印形式.输出位置! 配置log4j.properties文件如下: ####### ...

  3. TxQueryRunner

    package cn.itcast.jdbc; import java.sql.Connection; import java.sql.SQLException; import org.apache. ...

  4. linux运维常见英文报错中文翻译(菜鸟必知)

    linux常见英文报错中文翻译(菜鸟必知) 1.command not found  命令没有找到 2.No such file or directory  没有这个文件或目录 3.Permissio ...

  5. 【MySQL】mysql出现错误“ Every derived table must have its own alias”

    Every derived table must have its own alias 这句话的意思是说每个派生出来的表都必须有一个自己的别名 一般在多表查询时,会出现此错误. 因为,进行嵌套查询的时 ...

  6. SQL如何获得本季度第一天、一年的第一天、本月的最后一天

    nterval 参数,具有以下设定值: 设置 描述 Year yy, yyyy 年 quarter qq, q 季 Month mm, m 月 dayofyear dy, y 一年的日数 Day dd ...

  7. MySQL Sleep进程

    MySQL中查询当前的连接数: mysql> show status like '%Threads_connected%'; +-------------------+-------+ | Va ...

  8. Oracle 12C -- 在相同的列的集合上创建多个索引

    在12C中,可以在相同的列的集合上创建多个索引,但是多个索引的类型要不同.同一时刻,只有一个是可见的. SQL> create table emp_tab as select * from em ...

  9. @property与@synthesize的差别

    上一篇文章我有讲到self.与_的差别,往往和这个问题相伴随的是我困惑的问题是"@property与@synthesize的差别" @property的使用方法 @interfac ...

  10. SpringBoot多跨域请求的支持(JSONP)

    在我们做项目的过程中,有可能会遇到跨域请求,所以需要我们自己组装支持跨域请求的JSONP数据,而在4.1版本以后的SpringMVC中,为我们提供了一个AbstractJsonpResponseBod ...