文章目錄
C++ bind淺析
使用過STL的人肯定知道,STL有一個適配器的概念,其中stack
, queue
都是適配器,底層使用其他的數據結構來實現。
除了容器有適配器之外,其實函數也提供了適配器,適配器的特點就是將一個類型改裝成爲擁有子集功能的新的類型。其中函數的適配器典型的就是通過std::bind
來實現。
本文我們看下std::bind
的原理。
1. bind1st
在瞭解std::bind
之前,我們先看一個簡單的情況std::bind1st
,這個函數相當我們可以綁定第一個值,可以將一個二元操作轉換爲一元操作。
1.1 基本使用
void bind_test()
{
auto is_large_ten = std::bind1st(std::less<int>(), 10);
std::cout << is_large_ten(5) << std::endl;
std::cout << is_large_ten(15) << std::endl;
}
int main()
{
bind_test();
return 0;
}
如果我們需要判斷一個數是否小於10,那麼因爲10是固定的,相當只要傳入一個變量即可,因此這裏使用函數適配器將std::less
適配成爲is_large_ten
.
1.2 std::bind1st
原理
其實這個函數是非常簡單的,返回一個對象給調用者,這個對象有如下特性:
- 保存被適配的函數。
- 保存被適配函數的第一個參數的值。
- 實現調用操作
result_type operator()(const argument_type& _Right)
. - 在調用操作中調用被適配函數,第一個參數爲綁定的值,第二個參數爲傳遞的參數。
template<class _Fn2>
class binder1st
: public unary_function<typename _Fn2::second_argument_type,
typename _Fn2::result_type>
{ // functor adapter _Func(stored, right)
public:
typedef unary_function<typename _Fn2::second_argument_type,
typename _Fn2::result_type> _Base;
typedef typename _Base::argument_type argument_type;
typedef typename _Base::result_type result_type;
binder1st(const _Fn2& _Func,
const typename _Fn2::first_argument_type& _Left)
: op(_Func), value(_Left)
{ // construct from functor and left operand
}
result_type operator()(const argument_type& _Right) const
{ // apply functor to operands
return (op(value, _Right));
}
result_type operator()(argument_type& _Right) const
{ // apply functor to operands
return (op(value, _Right));
}
protected:
_Fn2 op; // the functor to apply
typename _Fn2::first_argument_type value; // the left operand
};
// TEMPLATE FUNCTION bind1st
template<class _Fn2,
class _Ty> inline
binder1st<_Fn2> bind1st(const _Fn2& _Func, const _Ty& _Left)
{ // return a binder1st functor adapter
typename _Fn2::first_argument_type _Val(_Left);
return (binder1st<_Fn2>(_Func, _Val));
}
從上面代碼我們可以很簡單的發現:
- 返回對象的類型爲
binder1st
. - 使用
typename _Fn2::first_argument_type value;
保存了綁定的值。 _Fn2 op;
定義了被適配的函數。- 提供調用操作符
result_type operator()(argument_type& _Right)
. - 調用操作中,使用被適配的函數,第一個值爲綁定的值
return (op(value, _Right));
.
與之類似,還存在一個綁定第二個參數的適配器爲std::bind2nd
,這個函數適配器的原理和std::bind1st
類似,這裏不再分析。
2 bind
標準庫中,認爲一元操作和二元操作是非常常用的算法操作,但是三元以及以上是極少的,因此標準庫中並沒有定義三元函數和其適配器。
但是提供了更加通用的東西std::bind
,來綁定參數信息。
2.1 使用
bind
是一個函數的適配器,可以將一個函數的參數特例化,如下:
int fun(int a, int b, int c, int d)
{
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
std::cout << d << std::endl;
return 0;
}
void bind_test()
{
auto f = std::bind(fun, std::placeholders::_1, std::placeholders::_2, 300, 400);
f(100, 200);
}
int main()
{
bind_test();
return 0;
}
其中std::placeholders::_1
是一個佔位符號。接下來我們看下std::bind
的具體實現原理。
2.2 std::bind 原理
std::bind
是一個模板函數,這個函數的主要目的是創建一個_Binder
函數適配器對象,代碼如下:
template<class _Fx,
class... _Types>
_NODISCARD 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 index_sequence_for<_Types...> _Seq;
typedef decay_t<_Fx> _First;
typedef tuple<decay_t<_Types>...> _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(_Invoker_ret<_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(_Invoker_ret<_Ret>(), _Seq(), \
_Mypair._Get_first(), _Mypair._Get_second(), \
_STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))); \
}
_CLASS_DEFINE_CONST(_BINDER_OPERATOR)
#undef _BINDER_OPERATOR
};
這個實現過程如下:
decay_t<_Fx> _First
: 這個聲明一個Binder
函數的類型。tuple<decay_t<_Types>...> _Second
: 綁定的參數的元組。_Compressed_pair<_First, _Second> _Mypair
將函數和參數元組組成一個pair。- 實現函數調用操作
auto operator()
.
所以結構信息如下:
2.3 operator() 實現
_Binder
的實現過程如下:
template<class... _Unbound> \
auto operator()(_Unbound&&... _Unbargs) CONST_OPT \
-> decltype(_Call_binder(_Invoker_ret<_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(_Invoker_ret<_Ret>(), _Seq(), \
_Mypair._Get_first(), _Mypair._Get_second(), \
_STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))); \
}
這個函數的返回值有點複雜,但是不是我們關係的,這裏主要看一下_Call_binder
的執行過程:
_Mypair._Get_first()
: 獲取到函數對象。_Mypair._Get_second()
: 獲取綁定的參數列表。forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))
: 參數元組。
_Call_binder
的實現如下:
template<class _Ret,
size_t... _Ix,
class _Cv_FD,
class _Cv_tuple_TiD,
class _Untuple> inline
auto _Call_binder(_Invoker_ret<_Ret>, index_sequence<_Ix...>,
_Cv_FD& _Obj, _Cv_tuple_TiD& _Tpl, _Untuple&& _Ut)
-> decltype(_Invoker_ret<_Ret>::_Call(_Obj, _Fix_arg(_STD get<_Ix>(_Tpl), _STD move(_Ut))...))
{ // bind() and bind<R>() invocation
return (_Invoker_ret<_Ret>::_Call(_Obj, _Fix_arg(_STD get<_Ix>(_Tpl), _STD move(_Ut))...));
}
參數信息如下:
調用函數的時候,會將_Ut元組中的參數替換Tpl中的佔位符,因此也就是說,佔位符位置可以隨意,如下:
int fun(int a, int b, int c, int d)
{
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
std::cout << d << std::endl;
return 0;
}
void bind_test()
{
auto f = std::bind(fun, 100, std::placeholders::_2, 300, std::placeholders::_1);
f(100, 200);
}
int main()
{
bind_test();
return 0;
}
3. 總結
從上來看,std::bind
其實就是一個函數的適配器,用來綁定其中的參數的特定值,實現了一個特化版本的函數。