文章目錄
C++ std::function技術淺談
std::function
是一個函數對象的包裝器,std::function
的實例可以存儲,複製和調用任何可調用的目標,包括:
- 函數。
- lamada表達式。
- 綁定表達式或其他函數對象。
- 指向成員函數和指向數據成員的指針。
當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
在什麼情況下可以使用,以及背後完成的事情。