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 _Functhis->_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 objectreturn (_Getimpl() == 0);}template <class _Fx>void _Reset(_Fx&& _Val){ // store copy of _Valif (!_Test_callable(_Val)){ // null member pointer/function pointer/std::functionreturn; // 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 upif (!_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 alignmentchar _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 objectreturn (_Getimpl() == _Getspace());}_Ptrt* _Getimpl() const noexcept{ // get pointer to objectreturn (_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 spacereturn (&_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 *thisreturn (_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 *thisreturn (::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 objectreturn (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) };
所以,我从简单考虑,之前没有写出这种继承关系,对实际理解应该没有什么影响。上述,就是我的简单介绍。