C++標準庫是日常應用中非常重要的庫,我們會用到C++標準庫的很多組件,C++標準庫的作用,不單單是一種可以很方便使用的組件,也是我們學習很多實現技巧的重要寶庫。我一直對C++很多組件的實現擁有比較強的興趣。最近花了一些時間,查看了C++中function類的實現,將其中的要點,寫在這裡(這裡只介紹 ...
C++標準庫是日常應用中非常重要的庫,我們會用到C++標準庫的很多組件,C++標準庫的作用,不單單是一種可以很方便使用的組件,也是我們學習很多實現技巧的重要寶庫。我一直對C++很多組件的實現擁有比較強的興趣。最近花了一些時間,查看了C++中function類的實現,將其中的要點,寫在這裡(這裡只介紹其中的一部分):
1.首先VC實現了將<Ret(T1, T2, ...)>這種類型的類型參數,改變為<Ret, T1, T2, ...>這種類型的類型參數。使用的方法如下:
template <class _Fty> class function : public _Get_function_impl<_Fty>::type { public: using _Mybase = _Get_function_impl<_Fty>::type; public: function() noexcept { // construct empty function wrapper } template <class _Fx, class = typename _Mybase::_Enable_if_callable_t<_Fx&, function>> function(_Fx _Func) { // construct wrapper holding copy of _Func this->_Reset(std::move(_Func)); } private: // 沒有其他的數據成員 };
不過,對於_Get_function_impl<_Fty>::type的實現,應該是編譯器額外處理(我不記得C++中有類似的語法),大致處理如下(如下代碼不能編譯通過):
template <class _Ret, class... _Types> struct _Get_function_impl<_Ret CALL_OPT (_Types...)> { using type = _Func_class<_Ret, _Types...>; };
參考以上代碼,也可以使用boost中的boost::typeindex,可以知道,function的實現,繼承於_Func_class,function的實現,使用了類似於Adapter的方式,具體的實現細節在_Func_class中。這點,可以參考上面的function構造函數。function中沒有直接定義operator()函數,operator()函數在_Func_class中定義,我們查看一下_Func_class函數:
using max_align_t = double; // most aligned type // size in pointers of std::function and std::any (roughly 3 pointers larger than std::string when building debug constexpr int _Small_object_num_ptrs = 6 + 16 / sizeof(void *); constexpr size_t _Space_size = (_Small_object_num_ptrs - 1) * sizeof(void*); template <class _Ret, class... _Types> class _Func_class { // implement function template public: using result_type = _Ret; using _Ptrt = _Func_base<_Ret, _Types...>; _Func_class() noexcept { // construct without stored object _Set(0); } _Ret operator()(_Types... _Args) const { if (_Empty()) { _Xbad_function_call(); } const auto _Impl = _Getimpl(); return (_Impl->_Do_call(_STD forward<_Types>(Args)...)); } protected: // 用於判斷傳入的函數對象可以調用_Types...表示的參數,並且返回_Ret類型的參數, // 而且不為function類型(至少上面的調用時是這個意思) template<class _Fx, class _Function> using _Enable_if_callable_t = enable_if_t<conjunction_v<negation<is_same<decay_t<_Fx>, _Function>>, _Is_invocable_r<_Ret, _Fx, _Types...>>>; bool _Empty() const _NOEXCEPT { // return true if no stored object return (_Getimpl() == 0); } template <class _Fx> void _Reset(_Fx&& _Val) { // store copy of _Val if (!_Test_callable(_Val)) { // null member pointer/function pointer/std::function return; // already empty } using _Impl = _Func_impl_no_alloc<decay_t<_Fx>, _Ret, _Types...>; _Reset_impl<_Impl>(std::forward<_Fx>(_Val), _Is_large<_Impl>()); } template <class _Myimpl, class _Fx> void _Reset_impl(_Fx&& _Val, true_type) { // store copy of _Val, large (dynamically allocated) _Set(_Global_new<_Myimpl>(std::foward<_Fx>(_Val))); } template <class _Myimpl, class _Fx> void _Reset_impl(_Fx&& _Val, false_type) { // store copy of _Val, small (locally stored) // placement operator new,將對象創建在_Mystorage所在的地址 _Set(::new (_Getspace()) _Myimpl(std::forward<_Fx>(_Val))); } void _Tidy() noexcept { // clean up if (!_Empty()) { // destroy callable object and maybe delete it _Getimpl()->_Delete_this(!_Local()); _Set(0); } } private: 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[_Small_object_num_ptrs]; // _Ptrs[_Small_object_num_ptrs - 1] is reserved }; _Storage _Mystorage; // 數據成員 bool _Local() const noexcept { // test for locally stored copy of object return (_Getimpl() == _Getspace()); } _Ptrt* _Getimpl() const noexcept { // get pointer to object return (_Mystorage._Ptrs[_Small_object_num_ptrs - 1]); } void _Set(_Ptrt* _Ptr) noexcept { // store pointer to object _Mystorage._Ptrs[_Small_object_num_ptrs - 1] = _Ptr; } void *_Getspace() noexcept { // get pointer to storage space return (&_Mystorage); } const void* _Getspace() const noexcept { return (&_Mystorage); } };
這個類只有一個數據成員,就是_Storage _Mystorage;其中_Storage是個union,union中兩個成員用了Dummy開頭,dummy的意思,就是只是為了實現某些目的,實際中並不會應用,_Dummy1的目的是為了讓這個對象最大對齊,_Dummy2說是用於別名,我從實現來看,更像用來表示多大的函數對象可以本地存儲,_Space_size表示的就是可以將函數對象直接存儲到_Func_class中最大的值,而_Ptrs[_Small_object_num_ptrs]的大小,剛好比_Dummy2對一個指針的長度大小,因為最後一個指針需要用了存儲實際函數的起始地址,這點,可以參考_Getimpl函數和_Set函數。這樣做的目的,應該是減少new的次數,因為new的次數過多,容易導致比較嚴重的碎片化,而且new本來速度也比不了在堆棧中分配記憶體的速度,不過,另一方面,從實現來看,一個function占用的大小,遠大於一個函數指針的大小,更遠大於一個沒有數據成員的函數對象的大小。如果對於記憶體占用有很大的要求,而且需要的函數對象又特別多,例如附帶函數的事件處理隊列等,需要慎重考慮一下。
說了這麼多,那麼C++中是如何做到在函數對象比較小的情況下,將函數對象存儲到本地,而比較大的時候,將函數對象在堆上分配呢?我們需要查看一下:
_Ptrt *_Ptrs[_Small_object_num_ptrs];中的_Ptrt,也就是_Func_base:
template <class _Rx, class... _Types> class _Func_base { // abstract base for implementation types public: virtual _Func_base* _Copy(void*) const = 0; virtual _Func_base* _Move(void*) const = 0; virtual _Fx _Do_call(Types&&...) = 0; virtual void _Delete_this(bool) _NOEXCEPT = 0; _Func_base() = default; _Func_base(const _Func_base&) = delete; _Func_base& operator=(const _Func_base&) = delete; // destructor non-virtual due to _Delete_this() }; template <class _Callable, class _Rx, class... _Types> class _Func_impl_no_alloc final : public _Func_base<_Rx, _Types...> { // derived class for specific implementation types that don't use allocators public: using _Mybase = _Func_base<_Rx, _Types...>; using _Nothrow_move = is_nothrow_move_constructible<_Callable>; template <class _Other, class = enable_if_t<!is_same_v<_Func_impl_no_alloc, decay_t<_Other>>>> explicit _Func_impl_no_alloc(_Other&& _Val) : _Callee(std::forward<_Other>(_Val)) { // construct } private: virtual _Mybase *_Copy(void *_Where) const override { // return clone of *this return (_Clone(_Where, _Is_large<_Func_impl_no_alloc>())); } _Mybase *_Clone(void *, true_type) const { // return clone of *this, large (dynamically allocated) return (_Global_new<_Func_impl_no_alloc>(_Callee)); } _Mybase *_Clone(void *_Where, false_type) const { // return clone of *this, small (locally stored) return (::new (_Where) _Func_impl_no_alloc(_Callee)); } virtual _Mybase *_Move(void *_Where) override { // return clone of *this return (::new (_Where) _Func_impl_no_alloc(std::move(_Callee))); } virtual _Rx _Do_call(_Types&&... _Args) override { return (_Invoker_ret<_Rx>::_Call(_Callee, std::forward<_Types>(_Args)...)); } virtual const void *_Get() const noexcept override { // return address of stored object return (std::addressof(_Callee)); } virtual void _Delete_this(bool _Dealloc) noexcept override { this->~_Func_impl_no_alloc(); if (_Dealloc) { _Deallocate<alignof(_Func_impl_no_alloc)>(this, sizeof(_Func_impl_no_alloc)); } } _Callable _Callee; };
我們認真查看上述代碼,不難發現,區分在_Copy函數中的_Is_large<_Func_impl_no_alloc>(),實際調用是:_Mybase *_Clone(void *, true_type),採用的是在堆中分配一段空間,而_Mybase *_Clone(void *_Where, false_type)採用的是placement operator new,將對象創建在本地。我們查看_Func_class中的Reset系列函數,也可以發現_Is_large的使用,以及堆棧分配和本地分配的區別。下麵,給出_Is_large的實現:
template <class _Impl> struct _Is_large : bool_constant<_Space_size < sizeof(_Impl) || !_Impl::_Nothrow_move::value> { // determine whether _Impl must be dynamically allocated };
其中true_type是bool_constant<true>,而false_type是bool_constant<false>,為不同的類型。最後,還要要簡單提及一下的是:_Func_class是繼承於public _Arg_types<_Types...>,而
// 這個類的目的是提供argument_type, first_argument_type, second_argument_type, // 因為C++17中已經為deprecated,所以,沒有查看的必要 template <class... _Types> struct _Arg_types { // provide argument_type, etc. (sometimes) };
所以,我從簡單考慮,之前沒有寫出這種繼承關係,對實際理解應該沒有什麼影響。上述,就是我的簡單介紹。