std::bind源码剖析

来源:互联网 发布:阿里云视频直播php 编辑:程序博客网 时间:2024/05/17 17:38

前期准备

  • C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include
  • Copyright (c) by P.J. Plauger. All rights reserved.
  • Consult your license regarding permissions and restrictions.
    V6.50:0009

bind

我们先来研究一下bind,这个bind实际上用途极为广泛,特别是在多线程的程序中,经常用它将函数进行包装,然后打包发送给工作线程,让工作线程去执行我们的任务。

代码范例

#include "stdafx.h"#include <functional>using namespace std;int myfunc(int a, int b, int c ){    int d = a + b + c;    return d;}int firstfunc(int a){    int b = a;    return b;}int main(){    auto myfunctemp = bind(myfunc, 1,2,3);    auto myfunctemp2 = bind(firstfunc, 4 );    myfunctemp();    myfunctemp2();    return 0;}

上面是个函数使用的范例,我们来看一下,模板背后的汇编故事,下面这个是第一个函数myfunc的生成实例。

ConsoleApplication1.exe!std::bind<int (__cdecl&)(int,int,int),int,int,int>(int(*)(int, int, int) _Func, int && <_Args_0>, int && <_Args_1>, int && <_Args_2>) 行 894     C++函数头对应的汇编位置为011854C0

接着是第二个函数firstfunc的生成实例

ConsoleApplication1.exe!std::bind<int (__cdecl&)(int),int>(int(*)(int) _Func,int && <_Args_0>) 行 893函数头对应的汇编位置为01185460

由此我们发现,根据同一模板,输入不同的特例生成不同的函数,执行不同的实例。
这是我们一种最为简单的bind的使用方法,然后接着我们详细分析一下其后面的逻辑过程。

bind函数

    // TEMPLATE FUNCTION bind (implicit return type)template<class _Fx,    class... _Types> inline    _Binder<_Unforced, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args)    {   // bind a callable object with an implicit return type    return (_Binder<_Unforced, _Fx, _Types...>(        _STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...));    }    // TEMPLATE FUNCTION bind (explicit return type)template<class _Ret,    class _Fx,    class... _Types> inline    _Binder<_Ret, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args)    {   // bind a callable object with an explicit return type    return (_Binder<_Ret, _Fx, _Types...>(        _STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...));    }

我们使用bind库的入口就是使用bind模板函数,实际上这个函数将我们传递的函数地址和函数参数做一个包装,构造一个类,返回个类的一个实例。

_Binder类

template<class _Ret,    class _Fx,    class... _Types>    class _Binder        : public _Binder_result_type<_Ret, _Fx>::type    {   // wrap bound callable object and argumentsprivate:    typedef make_integer_sequence<size_t, sizeof...(_Types)> _Seq;    typedef typename decay<_Fx>::type _First;    typedef tuple<typename decay<_Types>::type...> _Second;    _Compressed_pair<_First, _Second> _Mypair;public:    explicit _Binder(_Fx&& _Func, _Types&&... _Args)        : _Mypair(_One_then_variadic_args_t(),            _STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...)        {   // construct from forwarded callable object and arguments        }#define _BINDER_OPERATOR(CONST_OPT) \    template<class... _Unbound> \        auto operator()(_Unbound&&... _Unbargs) CONST_OPT \        -> decltype(_Call_binder(_Forced<_Ret>(), _Seq(), \            _Mypair._Get_first(), _Mypair._Get_second(), \            _STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))) \        {   /* invoke bound callable object with bound/unbound arguments */ \        return (_Call_binder(_Forced<_Ret>(), _Seq(), \            _Mypair._Get_first(), _Mypair._Get_second(), \            _STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))); \        }_CLASS_DEFINE_CONST(_BINDER_OPERATOR)#undef _BINDER_OPERATOR    };

看到这个类,虽然仅仅寥寥数行代码,但是用到的新的c++特性却极多
* 首先,支持可变模板参数。
* 使用make_integer_sequence,这是c++14的特性。
* 使用decay和tuple来定义函数和参数类型
* 使用新型的Compressed_pair来实际存储一对decay和tuple。
* explicit禁止隐式类型转换
* auto 自动识别类型
* decltype通过表达式确定类型,->函数返回值类型后置。
下面我们详细分析和介绍这里面的知识点。

decay

decay,用来定义函数的存储类型,typedef typename decay<_Fx>::type _First;
位于type_traits文件中,这里使用了模板元编程技术。

    // TEMPLATE CLASS decaytemplate<class _Ty>    struct decay    {   // determines decayed version of _Ty    typedef typename remove_reference<_Ty>::type _Ty1;    typedef typename _If<is_array<_Ty1>::value,        typename remove_extent<_Ty1>::type *,        typename _If<is_function<_Ty1>::value,            typename add_pointer<_Ty1>::type,            typename remove_cv<_Ty1>::type>::type>::type type;    };

它对于普通类型来说std::decay是移除引用和cv符,大大简化了我们的书写。除了普通类型之外,std::decay还可以用于数组和函数,具体的转换规则是这样的:

  • 先移除T类型的引用,得到类型U,U定义为remove_reference::type。
  • 如果is_array::value为 true,修改类型type为remove_extent::type *。
  • 否则,如果is_function::value为 true,修改类型type将为add_pointer::type。
  • 否则,修改类型type为 remove_cv::type。

std::decay的基本用法:

typedef std::decay<int>::type A;           // inttypedef std::decay<int&>::type B;          // inttypedef std::decay<int&&>::type C;         // inttypedef std::decay<constint&>::type D;    // inttypedef std::decay<int[2]>::type E;        // int*typedef std::decay<int(int)>::type F;      // int(*)(int)

std::decay除了移除普通类型的cv符的作用之外,还可以将函数类型转换为函数指针类型,从而将函数指针变量保存起来,以便在后面延迟执行,比如下面的例子。

template<typename F>struct SimpFunction{    using FnType = typename std::decay<F>::type;//先移除引用再添加指针    SimpFunction(F& f) : m_fn(f){}    void Run()    {        m_fn();    }    FnType m_fn;};

tuple

tuple元组定义了一个有固定数目元素的容器,其中的每个元素类型都可以不相同,这与其他容器有着本质的区别.是对pair的泛化。

typedef tuple<typename decay<_Types>::type...> _Second;_Compressed_pair<_First, _Second> _Mypair;

首先使用decay来提取出参数的类型,定义出存放在tuple中的类型,这个类型typedef成Second.具体的参数类型是存放在Compressed_pair中的。

make_integer_sequence

此模板用于展开tuple,实现函数参数的输入。
为了方便使用,使用了模板别名特性声明了辅助使用的别名模板

template<std::size_t... Ints>using index_sequence = std::integer_sequence<std::size_t, Ints...>;template<class T, T N>using make_integer_sequence = std::integer_sequence<T, /* a sequence 0, 1, 2, ..., N-1 */>;template<std::size_t N>using make_index_sequence = make_integer_sequence<std::size_t, N>;template<class... T>using index_sequence_for = std::make_index_sequence<sizeof...(T)>;

_Compressed_pair

这个compressed_pair是对pair的升级,同样能容纳任意两个元素,但是它针对空类成员进行了特别的优化,可以“压缩”pair的大小。

template<class _Ty1,    class _Ty2,    bool = is_empty<_Ty1>::value && !is_final<_Ty1>::value>    class _Compressed_pair final        : private _Ty1    {   // store a pair of values, deriving from empty firstprivate:    _Ty2 _Myval2;    typedef _Ty1 _Mybase;   // for visualization

这是一个模板泛化的版本,如果_Ty1是空类的情况下,就将这个空类作为继承处理,编译器会进行空基类优化处理,将空的基类内存省去,以节省空间。
而对于非空模板类就有一个特化的版本

template<class _Ty1,    class _Ty2>    class _Compressed_pair<_Ty1, _Ty2, false> final    {   // store a pair of values, not deriving from firstprivate:    _Ty1 _Myval1;    _Ty2 _Myval2;

这种特化的版本就直接声明两个类成员变量使用。

operator()

类重载操作符(),这样直接使用类实例就可以如函数调用一下操作了,其实这是STL标准库中的一种functor形式,一种仿函数。
_Binder类中的operator()函数使用了函数类型后置,因为此时的函数返回类型比较长,所以使用返回类型后置的方式,并且使用了auto来占位。auto可以自动获得类型。

这个函数中将核心功能交给了_Call_binder。

_Call_binder模板函数

template<class _Ret,    size_t... _Ix,    class _Cv_FD,    class _Cv_tuple_TiD,    class _Untuple> inline    auto _Call_binder(_Forced<_Ret> _Fr, integer_sequence<size_t, _Ix...>,        _Cv_FD& _Obj, _Cv_tuple_TiD& _Tpl, _Untuple&& _Ut)    -> decltype(_Invoke_ret(_Fr, _Obj, _Fix_arg(        _STD get<_Ix>(_Tpl), _STD move(_Ut))...))    {   // bind() and bind<R>() invocation    (void) _Tpl;    // TRANSITION, VSO#181496    (void) _Ut;    return (_Invoke_ret(_Fr, _Obj, _Fix_arg(        _STD get<_Ix>(_Tpl), _STD move(_Ut))...));    }

上面这个函数是函数调用的具体执行的模板函数,使用了auto和函数返回类型后置,decltype计算后面的表达式类型又不具体的执行表达式。
我们看一下例子函数中第一个函数到这里的时候的实际生成的函数实例是什么,如下所示

ConsoleApplication1.exe!std::_Call_binder<std::_Unforced,0,1,2,int (__cdecl*)(int,int,int),std::tuple<int,int,int>,std::tuple<> >(std::_Forced<std::_Unforced,0> _Fr, std::integer_sequence<unsigned int,0,1,2> __formal, int(*)(int, int, int) & _Obj, std::tuple<int,int,int> & _Tpl, std::tuple<> && _Ut) 行 829 C++

接下来使用_Invoke_ret来执行具体的调用,传入返回值类型,函数名,使用get来展开tuple以形成参数类型列表,实参。

_Invoke_ret函数模板

承接上面_Call_binder函数,调用_Invoke_ret,前者主要进行参数传递和tuple展开,以将原始调用信息传递给后者。

template<class _Cv_void,    class... _Valtys> inline    void _Invoke_ret(_Forced<_Cv_void, true>, _Valtys&&... _Vals)    {   // INVOKE, "implicitly" converted to void    _STD invoke(_STD forward<_Valtys>(_Vals)...);    }template<class _Rx,    class... _Valtys> inline    _Rx _Invoke_ret(_Forced<_Rx, false>, _Valtys&&... _Vals)    {   // INVOKE, implicitly converted to _Rx    return (_STD invoke(_STD forward<_Valtys>(_Vals)...));    }template<class... _Valtys> inline    auto _Invoke_ret(_Forced<_Unforced, false>, _Valtys&&... _Vals)    -> decltype(_STD invoke(_STD forward<_Valtys>(_Vals)...))    {   // INVOKE, unchanged    return (_STD invoke(_STD forward<_Valtys>(_Vals)...));    }

这个函数模板提供三个特化的版本,一个是显式转换返回值类型到void,一个是特定的_Rx,最后一个是自动推导的。

ConsoleApplication1.exe!std::_Invoke_ret<int (__cdecl*&)(int,int,int),int &,int &,int &>(std::_Forced<std::_Unforced,0> __formal, int(*)(int, int, int) & <_Vals_0>, int & <_Vals_1>, int & <_Vals_2>, int & <_Vals_3>) 行 1498    C++

我们看到范例中第一个函数的具体调用实例如上所示,这是调试下打开堆栈窗口时看到的,代表着一个实例。
_Invoke_ret将函数指针和函数参数都转换为一个可变参数组了,然后向下继续调用invoke完成实际的调用函数的过程。

invoke

invoke将_Invoke_ret传递过来的可变参数组进行分开识别,分出函数指针和参数。

template<class _Callable,    class... _Types> inline    auto invoke(_Callable&& _Obj, _Types&&... _Args)    -> decltype(_Invoker<_Callable, _Types...>::_Call(        _STD forward<_Callable>(_Obj), _STD forward<_Types>(_Args)...))    {   // INVOKE a callable object    return (_Invoker<_Callable, _Types...>::_Call(        _STD forward<_Callable>(_Obj), _STD forward<_Types>(_Args)...));    }

同样使用了auto和decltype来简化书写,分开函数指针和参数,具体任务交由_Invoker::_Call来做具体的执行

_Invoker

template<class _Callable,    class... _Types>    struct _Invoker;template<class _Callable>    struct _Invoker<_Callable>        : _Invoker_functor    {   // zero arguments    };template<class _Callable,    class _Ty1,    class... _Types2>    struct _Invoker<_Callable, _Ty1, _Types2...>        : _Invoker1<_Callable, _Ty1>    {   // one or more arguments    };

通过这个类来具体的区分有参和无参两大类,提供了两个特化版本,而泛化版本并没有实际的用途,因为只有两种可能,要么有参要么无参。
* 如果无参,我们直接继承_Invoker_functor,说明这是一个仿函数,通过_Invoker_functor中的_Call来完成实际的函数调用过程。
* 如果有参,那么我们需要进一步判断,这步判断交给_Invoker1来做具体的处理。

_Invoker1

template<class _Callable,    class _Ty1,    class _Decayed = typename decay<_Callable>::type,    bool _Is_pmf = is_member_function_pointer<_Decayed>::value,    bool _Is_pmd = is_member_object_pointer<_Decayed>::value>    struct _Invoker1;template<class _Callable,    class _Ty1,    class _Decayed>    struct _Invoker1<_Callable, _Ty1, _Decayed, true, false>        : _If<is_base_of<            typename _Is_memfunptr<_Decayed>::_Class_type,            typename decay<_Ty1>::type>::value,        _Invoker_pmf_object,        _Invoker_pmf_pointer>::type    {   // pointer to member function    };template<class _Callable,    class _Ty1,    class _Decayed>    struct _Invoker1<_Callable, _Ty1, _Decayed, false, true>        : _If<is_base_of<            typename _Is_member_object_pointer<_Decayed>::_Class_type,            typename decay<_Ty1>::type>::value,        _Invoker_pmd_object,        _Invoker_pmd_pointer>::type    {   // pointer to member data    };template<class _Callable,    class _Ty1,    class _Decayed>    struct _Invoker1<_Callable, _Ty1, _Decayed, false, false>        : _Invoker_functor    {   // function object    };

如果我们有参数,那么我们区分成三种类型,这里有三种特化的版本,一个是指向成员函数,一个是指向成员数据对象指针,一个是普通函数对象。
我们首先看泛化版本:
* 首先通过typename decay<_Callable>::type将函数指针朽化,然后根据decay的类型进一步判断。
* is_member_function_pointer来判断是否是成员函数
* is_member_object_pointer来判断是否是对象指针
* 如果都不是,那么是普通函数。
这样我们就产生了三小类不同的版本。

_Call 普通函数

到这里,我们分析到了最后一步,这是最后一步_Call调用。我们分析比较简单的一个版本,普通函数的版本。

struct _Invoker_functor    {   // INVOKE a function object    template<class _Callable,        class... _Types>        static auto _Call(_Callable&& _Obj, _Types&&... _Args)        -> decltype(_STD forward<_Callable>(_Obj)(            _STD forward<_Types>(_Args)...))        {   // INVOKE a function object        return (_STD forward<_Callable>(_Obj)(            _STD forward<_Types>(_Args)...));        }    };

这里直接通过函数指针调用用户绑定的函数,回到用户的代码逻辑上。_Obj是函数指针,而_Args是函数参数。

_Call 成员函数指针

如果我们绑定的是成员函数,那么又是一个什么样的情况呢,范例代码如下:

struct Foo {    void print_sum(int n1, int n2)    {        std::cout << n1 + n2 << '\n';    }    int data = 10;};int main(){    Foo foo;    auto f = std::bind( &Foo::print_sum, &foo, 1, 2 );    f();    return 0;}

我们看一下进入_Call调用之前invoke模板函数的实例结果:

ConsoleApplication1.exe!std::invoke<void (__thiscall Foo::*&)(int,int),Foo * &,int &,int &>(void(Foo::*)(int, int) & _Obj, Foo * & <_Args_0>, int & <_Args_1>, int & <_Args_2>) 行 1466    C++

这里我们会发现,__cdecl调用过程变成了__thiscall调用过程,另外有着三个参数,第一个参数是函数的this指针,其他两个参数是成员函数的参数。那么它会调用哪个版本的_Call呢?他会是如下这个成员函数指针call的版本

struct _Invoker_pmf_pointer    {   // INVOKE a pointer to member function on a [smart] pointer    template<class _Decayed,        class _Ty1,        class... _Types2>        static auto _Call(_Decayed _Pmf, _Ty1&& _Arg1, _Types2&&... _Args2)        -> decltype(((*_STD forward<_Ty1>(_Arg1)).*_Pmf)(            _STD forward<_Types2>(_Args2)...))        {   // INVOKE a pointer to member function on a [smart] pointer        return (((*_STD forward<_Ty1>(_Arg1)).*_Pmf)(            _STD forward<_Types2>(_Args2)...));        }    };

这里通过this指针 _Arg1来寻址成员函数的地址,进而传递参数,完成成员函数的调用。

_Call 类型

这里的bind的call共区分出两大类三小类共五个不同版本的call

struct _Invoker_pmf_object    {   // INVOKE a pointer to member function on an object    ....struct _Invoker_pmf_pointer    {   // INVOKE a pointer to member function on a [smart] pointer    ....struct _Invoker_pmd_object    {   // INVOKE a pointer to member data on an object    ....struct _Invoker_pmd_pointer    {   // INVOKE a pointer to member data on a [smart] pointer    ....struct _Invoker_functor    {   // INVOKE a function object    ....

在此文章中涉及到普通函数的call和成员函数作为指针的call,两种主要方式。

bind中的元编程技术

  • 模板偏特化: 编译期的if和else 。比如:_Invoker元函数,主要使用type_traits库,再比如_If<>。
  • 可变模板参数:编译期的for的一种方式(模板参数推导),比如:tuple的展开 _STD get<_Ix>(_Tpl
  • 各种元数据的定义:

    1. enum、static const,用来定义编译期的整数常量;
    2. typedef/using,用于定义元数据;
    3. T、Args…,声明元数据类型;
    4. template,主要用于定义元函数;
  • 模板参数类型推导:比如decay

0 0