注:下面的代码段单击即可显示
在之前 对std::bind生命周期的探究 中,提到了 std::bind 调用过程中,会使用 std::tuple<class… Args> 将需要的参数保存起来,而这种情况下,传引用则会失去本来的意义. 对于需要传引用的场合,需要显式地使用 std::ref<T> 对传引用的对象进行包裹,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include<functional> using namespace std; void modify(int& value) { value+=1; } int main() { int b = 1; printf("==Test 1==\n"); auto func = bind(&modify, b); printf("Before : %d\n", b); // 1 func(); printf("After : %d\n", b); // 1 printf("==Test 2==\n"); b = 1; auto func2 = bind(&modify, std::ref(b)); printf("Before : %d\n", b); // 1 func2(); printf("After : %d\n", b); // 2 return 0; } |
那么, std::ref 是如何实现引用的传递的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
template<class _Ty> class reference_wrapper : public _Weak_types<_Ty>::type { // stand-in for an assignable reference public: static_assert(is_object<_Ty>::value || is_function<_Ty>::value, "reference_wrapper<T> requires T to be an object type " "or a function type."); typedef _Ty type; reference_wrapper(_Ty& _Val) _NOEXCEPT : _Ptr(_STD addressof(_Val)) { // construct } operator _Ty&() const _NOEXCEPT { // return reference return (*_Ptr); } _Ty& get() const _NOEXCEPT { // return reference return (*_Ptr); } template<class... _Types> auto operator()(_Types&&... _Args) const -> decltype(_STD invoke(get(), _STD forward<_Types>(_Args)...)) { // invoke object/function return (_STD invoke(get(), _STD forward<_Types>(_Args)...)); } reference_wrapper(_Ty&&) = delete; private: _Ty *_Ptr; }; // TEMPLATE FUNCTIONS ref AND cref template<class _Ty> inline reference_wrapper<_Ty> ref(_Ty& _Val) _NOEXCEPT { // create reference_wrapper<_Ty> object return (reference_wrapper<_Ty>(_Val)); } |
可以发现, std::ref 产生了一个储存 _Val 指针的对象,当需要的时候再转换回 _Ty& 引用类型(小Tip:这里可以发现它使用了 std::addressof 函数,而不是通常的取地址运算符&,这是因为运算符 & 可以被重载,需要考虑到这种情况.而 std::addressof 的一种可行方式是,把输入的对象转换为 char& ,进行取址,再转换回 T* 类型,就不会调用运算符重载了.). 可以发现, std::ref 返回的类型,是存在 _Ty& 的隐式转换的,那么可以直接尝试传入 std::ref 返回的对象,使其自动转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include<functional> using namespace std; void modify(int& value) { value+=1; } int main() { int b = 1; auto refObj = std::ref(b); modify(refObj); return 0; } |
上面的代码是可以通过编译的,也就意味着直接传参是可行的.不过在 std::bind 中,并没有采用这种方法,而是采用模板识别来进行特殊处理.让我们来梳理一下 std::bind 的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// 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<class _Ret, class _Fx, class... _Types> class _Binder : public _Binder_result_type<_Ret, _Fx>::type { // wrap bound callable object and arguments private: 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 }; |
std::bind 函数会返回一个 _Binder 对象,在其运算符()的重载中会利用 _Call_binder 去调用原函数.需要注意的是上面定义的 _Seq 类型,那定义了一个特化的 std::make_integer_sequence() 函数,这个函数会返回 std::integer_sequence 类型,通常用于产生一个类型为T的参数序列(T…).上面的代码通过 sizeof…() 获取了函数参数的个数,利用 _Seq 产生参数序列,以便后面对函数的参数进行填入.
接下来我们进一步看看 _Call_binder 里面是怎么回事:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
template<class _Cv_TiD, class _Untuple> inline auto _Fix_arg(_Cv_TiD& _Tid, _Untuple&& _Ut) -> decltype(_Select_fixer<_Cv_TiD>::_Fix(_Tid, _STD move(_Ut))) { // translate an argument for bind return (_Select_fixer<_Cv_TiD>::_Fix(_Tid, _STD move(_Ut))); } // TEMPLATE FUNCTION _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))...)); } |
在_Call_binder的代码中,就根据之前产生的 integer_sequence 进行展开,对之前 std::tuple 中保存的每个参数调用 _Fix_arg进行额外处理(此处出现的 std::get<I,class… Types>,是用于获取 std::tuple 中第 I 个元素的函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
// TEMPLATE CLASS _Unrefwrap template<class _Ty> struct _Unrefwrap_helper { // leave unchanged if not a reference_wrapper typedef _Ty type; static constexpr bool _Is_refwrap = false; }; template<class _Ty> struct _Unrefwrap_helper<reference_wrapper<_Ty> > { // make a reference from a reference_wrapper typedef _Ty& type; static constexpr bool _Is_refwrap = true; }; template<class _Ty> struct _Unrefwrap { // decay, then unwrap a reference_wrapper typedef typename decay<_Ty>::type _Ty1; typedef typename _Unrefwrap_helper<_Ty1>::type type; static constexpr bool _Is_refwrap = _Unrefwrap_helper<_Ty1>::_Is_refwrap; }; // TEMPLATE FUNCTION _Fix_arg template<class _Cv_TiD, bool = _Unrefwrap<_Cv_TiD>::_Is_refwrap, bool = is_bind_expression<_Cv_TiD>::value, int = is_placeholder<_Cv_TiD>::value> struct _Select_fixer; template<class _Cv_TiD> struct _Select_fixer<_Cv_TiD, true, false, 0> { // reference_wrapper fixer template<class _Untuple> static auto _Fix(_Cv_TiD& _Tid, _Untuple&&) -> typename _Cv_TiD::type& { // unwrap a reference_wrapper return (_Tid.get()); } }; template<class _Cv_TiD> struct _Select_fixer<_Cv_TiD, false, true, 0> { // nested bind fixer template<class _Untuple, size_t... _Jx> static auto _Apply(_Cv_TiD& _Tid, _Untuple&& _Ut, integer_sequence<size_t, _Jx...>) -> decltype(_Tid(_STD get<_Jx>(_STD move(_Ut))...)) { // call a nested bind expression return (_Tid(_STD get<_Jx>(_STD move(_Ut))...)); } template<class _Untuple> static auto _Fix(_Cv_TiD& _Tid, _Untuple&& _Ut) -> decltype(_Apply(_Tid, _STD move(_Ut), make_integer_sequence<size_t, tuple_size<_Untuple>::value>())) { // call a nested bind expression return (_Apply(_Tid, _STD move(_Ut), make_integer_sequence<size_t, tuple_size<_Untuple>::value>())); } }; template<class _Cv_TiD> struct _Select_fixer<_Cv_TiD, false, false, 0> { // identity fixer template<class _Untuple> static _Cv_TiD& _Fix(_Cv_TiD& _Tid, _Untuple&&) { // pass a bound argument as an lvalue (important!) return (_Tid); } }; template<class _Cv_TiD, int _Jx> struct _Select_fixer<_Cv_TiD, false, false, _Jx> { // placeholder fixer static_assert(_Jx > 0, "invalid is_placeholder value"); template<class _Untuple> static auto _Fix(_Cv_TiD&, _Untuple&& _Ut) -> decltype(_STD get<_Jx - 1>(_STD move(_Ut))) { // choose the Jth unbound argument (1-based indexing) return (_STD get<_Jx - 1>(_STD move(_Ut))); } }; template<class _Cv_TiD, class _Untuple> inline auto _Fix_arg(_Cv_TiD& _Tid, _Untuple&& _Ut) -> decltype(_Select_fixer<_Cv_TiD>::_Fix(_Tid, _STD move(_Ut))) { // translate an argument for bind return (_Select_fixer<_Cv_TiD>::_Fix(_Tid, _STD move(_Ut))); } |
_Fix_arg 是对几种特殊情况进行了处理:
需要注意的是 _Select_Fixer 的定义,在定义处使用模板参数的默认值,将几种特殊情况的检测,与下面的模板特化,联系在了一起.以 std::ref 的检测为例, _Select_fixer 的第二个模板参数的值,是 _Unrefwarp 检测是否为 std::ref 类型的结果,对应着后面 _Select_fixer<_Cv_TiD, true, false, 0> 的特化.在 std::ref 的处理中,手动转换回了 _Cv_TiD::type& 引用.
PS:模板特化博大精深……
PS2:扩展阅读:Parameter Pack