C++ std::function技術淺談

C++ std::function技術淺談

std::function是一個函數對象的包裝器,std::function的實例可以存儲,複製和調用任何可調用的目標,包括:

  1. 函數。
  2. lamada表達式。
  3. 綁定表達式或其他函數對象。
  4. 指向成員函數和指向數據成員的指針。

std::function對象沒有初始化任何實際的可調用元素,調用std::function對象將拋出std::bad_function_call異常。

本文我們來談一下std::function的實現原理。

1. std::function簡介

在討論其原理的時候,我們來熟悉一下這個東西是怎麼使用的,C++標準庫詳細說明了這個的基本使用http://www.cplusplus.com/reference/functional/function/.

這裏我們大概總結一下。

1.1 Member types

成員類型 說明
result_type 返回類型
argument_type 如果函數對象只有一個參數,那麼這個代表參數類型。
first_argument_type 如果函數對象有兩個個參數,那麼這個代表第一個參數類型。
second_argument_type 如果函數對象有兩個個參數,那麼這個代表第二個參數類型。

1.2 Member functions

成員函數聲明 說明
constructor 構造函數:constructs a new std::function instance
destructor 析構函數: destroys a std::function instance
operator= 給定義的function對象賦值
operator bool 檢查定義的function對象是否包含一個有效的對象
operator() 調用一個對象

1.3 基本使用

#include <iostream>
#include <functional>

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;
}

class CCaller
{
public:
	int operator()(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;
	}
};
int main()
{
	CCaller Caller;
	std::function<int(int, int, int, int)> f;

	f = [](int a, int b, int c, int d) -> int
	{
		std::cout << a << std::endl;
		std::cout << b << std::endl;
		std::cout << c << std::endl;
		std::cout << d << std::endl;
		return 0;
	};
	f(1, 2, 3, 4);

	f = Caller;
	f(10, 20, 30, 40);

	f = fun;
	f(100, 200, 300, 400);

    return 0;
}

從上面我們可以發現,std::function可以表示函數,lamada,可調用類對象。

2. std::function實現

在標準庫中STL設計爲如下:

template<class... _Types>
	struct _Arg_types
	{	// provide argument_type, etc. (sometimes)
	};

template<class _Ty1>
	struct _Arg_types<_Ty1>
	{	// provide argument_type, etc. (sometimes)
	typedef _Ty1 argument_type;
	};

template<class _Ty1,
	class _Ty2>
	struct _Arg_types<_Ty1, _Ty2>
	{	// provide argument_type, etc. (sometimes)
	typedef _Ty1 first_argument_type;
	typedef _Ty2 second_argument_type;
	};

template<class _Ret,
	class... _Types>
	class _Func_class
		: public _Arg_types<_Types...>
	{	// implement function template
public:
	typedef _Ret result_type;

	typedef _Func_class<_Ret, _Types...> _Myt;
	typedef _Func_base<_Ret, _Types...> _Ptrt;
    
private:
	bool _Local() const _NOEXCEPT
		{	// test for locally stored copy of object
		return (_Getimpl() == _Getspace());
		}

	union _Storage
		{	// storage for small objects (basic_string is small)
		max_align_t _Dummy1;	// for maximum alignment
		char _Dummy2[_Space_size];	// to permit aliasing
		_Ptrt *_Ptrs[_Num_ptrs];	// _Ptrs[_Num_ptrs - 1] is reserved
		};
        _Storage _Mystorage;
        };

template<class _Ret, \
	class... _Types> \
	struct _Get_function_impl<_Ret CALL_OPT (_Types...)> \
	{	/* determine type from argument list */ \
	typedef _Func_class<_Ret, _Types...> type; \
	};

template<class _Fty>
	class function
		: public _Get_function_impl<_Fty>::type
	{	// wrapper for callable objects
public:
	typedef function<_Fty> _Myt;
    };

上面的std::function繼承關係比較簡單,主要使用

union _Storage
{
    // storage for small objects (basic_string is small)
    max_align_t _Dummy1;	// for maximum alignment
    char _Dummy2[_Space_size];	// to permit aliasing
    _Ptrt *_Ptrs[_Num_ptrs];	// _Ptrs[_Num_ptrs - 1] is reserved
};

這個來存儲我們設置的可調用對象,我們從std::function的使用過程看一下整個原理。

2.1 函數對象賦值

我們使用的時候一般使用f = Caller;來設置函數對象,我們看下這個的實現過程。

template<class _Fx>
		_Myt& operator=(reference_wrapper<_Fx> _Func) _NOEXCEPT
{
    // assign wrapper holding reference_wrapper to function object
    this->_Tidy();
    this->_Reset(_Func);
    return (*this);
}

我們看this->_Reset(_Func)這個函數,因爲這個纔是設置函數可調用對象的東西。

void _Set(_Ptrt *_Ptr) _NOEXCEPT
{	// store pointer to object
	_Mystorage._Ptrs[_Num_ptrs - 1] = _Ptr;
}

void _Reset_impl(_Fx&& _Val, const _Alloc& _Ax,
	_Myimpl *, _Alimpl& _Al, false_type)
{	// store copy of _Val with allocator, small (locally stored)
	_Myimpl *_Ptr = static_cast<_Myimpl *>(_Getspace());
	_Al.construct(_Ptr, _STD forward<_Fx>(_Val), _Ax);
	_Set(_Ptr);
}

template<class _Fx,
class _Alloc>
	void _Reset_alloc(_Fx&& _Val, const _Alloc& _Ax)
{	// store copy of _Val with allocator
	if (!_Test_callable(_Val))
	{	// null member pointer/function pointer/std::function
		return;	// already empty
	}

	typedef typename decay<_Fx>::type _Decayed;
	typedef _Func_impl<_Decayed, _Alloc, _Ret, _Types...> _Myimpl;
	_Myimpl *_Ptr = 0;

	typedef _Wrap_alloc<_Alloc> _Alimpl0;
	typedef typename _Alimpl0::template rebind<_Myimpl>::other _Alimpl;
	_Alimpl _Al(_Ax);

	_Reset_impl(_STD forward<_Fx>(_Val), _Ax,
		_Ptr, _Al, _Is_large<_Myimpl>());
}

template<class _Fx>
		void _Reset(_Fx&& _Val)
{
    // store copy of _Val
    _Reset_alloc(_STD forward<_Fx>(_Val), allocator<int>());
}

這個代碼的主要意思就是創建一個_Func_impl<_Decayed, _Alloc, _Ret, _Types...>指針,然後賦值_Mystorage._Ptrs[_Num_ptrs - 1] = _Ptr;

設置之後,我們看下調用操作怎麼完成。

2.2 operator() 的實現

調用操作主要是通過operator()來實現的,我們看下這個的實現過程。

_Ptrt *_Getimpl() const _NOEXCEPT
{	// get pointer to object
	return (_Mystorage._Ptrs[_Num_ptrs - 1]);
}

_Ret operator()(_Types... _Args) const
{	// call through stored object
	if (_Empty())
		_Xbad_function_call();
	return (_Getimpl()->_Do_call(_STD forward<_Types>(_Args)...));
}

因此,我們是通過_Func_impl<_Decayed, _Alloc, _Ret, _Types...>轉發了調用操作_Do_call

2.3 _Func_impl的實現

class _Func_impl
	: public _Func_base<_Rx, _Types...>
{	// derived class for specific implementation types
public:
	typedef _Func_impl<_Callable, _Alloc, _Rx, _Types...> _Myt;
	typedef _Func_base<_Rx, _Types...> _Mybase;
	typedef _Wrap_alloc<_Alloc> _Myalty0;
	typedef typename _Myalty0::template rebind<_Myt>::other _Myalty;
	typedef is_nothrow_move_constructible<_Callable> _Nothrow_move;


	virtual _Rx _Do_call(_Types&&... _Args)
	{	// call wrapped function
		return (_Invoke_ret(_Forced<_Rx>(), _Callee(),
			_STD forward<_Types>(_Args)...));
	}

    _Compressed_pair<_Alloc, _Callable> _Mypair;
};

_Func_impl這個類通過_Do_call來轉發函數對象的調用操作。

3. 總結

這裏我們看下std::function的結構信息,如下:
在這裏插入圖片描述
從這裏我們發現_Storage大小爲:

const int _Num_ptrs = 6 + 16 / sizeof (void *);
const size_t _Space_size = (_Num_ptrs - 1) * sizeof (void *);

_Num_ptrs值爲10.

如果我們賦值的對象有成員變量會是什麼情況呢?例如如下:


class CCaller
{
public:
	int operator()(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;
	}

	int a = 1;
	int b = 10;
	int c = 100;
};
int main()
{
	CCaller Caller;
	std::function<int(int, int, int, int)> f;

	f = Caller;
	f(10, 20, 30, 40);
    return 0;
}

內存結構如下:
在這裏插入圖片描述
由此我們可以發現std::function是利用一個_Compressed_pair<_Alloc, _Callable> _Mypair;拷貝了元素的數據信息。

主要的初始化過程爲:

emplate<class _Fx,
class _Alloc>
	void _Reset_alloc(_Fx&& _Val, const _Alloc& _Ax)
{	// store copy of _Val with allocator
	if (!_Test_callable(_Val))
	{	// null member pointer/function pointer/std::function
		return;	// already empty
	}

	typedef typename decay<_Fx>::type _Decayed;
	typedef _Func_impl<_Decayed, _Alloc, _Ret, _Types...> _Myimpl;
	_Myimpl *_Ptr = 0;

	typedef _Wrap_alloc<_Alloc> _Alimpl0;
	typedef typename _Alimpl0::template rebind<_Myimpl>::other _Alimpl;
	_Alimpl _Al(_Ax);

	_Reset_impl(_STD forward<_Fx>(_Val), _Ax,
		_Ptr, _Al, _Is_large<_Myimpl>());
}

其中decay<_Fx>::type定義了_Compressed_pair<_Alloc, _Callable> _Mypair;_Callable的類型,這個聲明如下(也就是去掉引用和其他屬性信息):

template<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::function的原理分析了,希望在後續的使用中,我們能夠知道std::function在什麼情況下可以使用,以及背後完成的事情。

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