C++ bind淺析

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原理

其實這個函數是非常簡單的,返回一個對象給調用者,這個對象有如下特性:

  1. 保存被適配的函數。
  2. 保存被適配函數的第一個參數的值。
  3. 實現調用操作result_type operator()(const argument_type& _Right).
  4. 在調用操作中調用被適配函數,第一個參數爲綁定的值,第二個參數爲傳遞的參數。
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));
	}

從上面代碼我們可以很簡單的發現:

  1. 返回對象的類型爲binder1st.
  2. 使用typename _Fn2::first_argument_type value;保存了綁定的值。
  3. _Fn2 op; 定義了被適配的函數。
  4. 提供調用操作符result_type operator()(argument_type& _Right).
  5. 調用操作中,使用被適配的函數,第一個值爲綁定的值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
	};

這個實現過程如下:

  1. decay_t<_Fx> _First : 這個聲明一個Binder函數的類型。
  2. tuple<decay_t<_Types>...> _Second : 綁定的參數的元組。
  3. _Compressed_pair<_First, _Second> _Mypair將函數和參數元組組成一個pair。
  4. 實現函數調用操作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的執行過程:

  1. _Mypair._Get_first() : 獲取到函數對象。
  2. _Mypair._Get_second() : 獲取綁定的參數列表。
  3. 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其實就是一個函數的適配器,用來綁定其中的參數的特定值,實現了一個特化版本的函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章