剖析STD合买源码搭建FUNCTION接口与实现

前言

为什么要剖析std::function呢?合买源码搭建Q2952777280 因为笔者最近在做一个std::function向单片机系统的移植与扩展。

后续还会有std::bind等标准库其他部分的移植。


一、std::function的原理与接口

1.1std::function是函数包装器

std::function,能存储任何符合模板参数的函数对象。换句话说,这些拥有一致参数类型、相同返回值类型(其实不必完全相同)的函数对象,可以由std::function 统一包装起来。函数对象的大小是任意的、不能确定的,而C++中的类型都是固定大小的,那么,如何在一个固定大小的类型中存储任意大小的对象呢?

实际上问题还不止存储那么简单。存储了函数对象,必定是要在某一时刻调用;函数对象不是在创建的时候调用,这个特性成为延迟调用;函数对象也是对象,需要处理好构造、拷贝、移动、析构等问题——这些也需要延迟调用,但总不能再用std::function 来解决吧?

既然std::function 能存储不同类型的函数对象,可以说它具有多态性。C++中体现多态性的主要是虚函数,继承与多态这一套体制是可以解决这个问题的。相关资料[1][2]中的实现利用了继承与多态,相当简洁。


1.2 C++注重运行时效率

利用继承与多态,我们可以让编译器帮我们搞定函数对象的析构。就这种实现而言,这是简洁而有效的方法。然而这种实现需要动态内存,在一些情况下不划算,甚至完全没有必要。C++11引入了lambda表达式,其本质也是函数对象。这个对象有多大呢?取决于捕获列表。你写lambda会捕获多少东西?很多情况下就只是一对方括号而已吧。在这种情况下,lambda表达式的对象大小只有1字节(因为不能是0字节),你却为了这没有内容的1字节要调用动态内存的函数?C++注重运行时效率,这种浪费是不能接受的。

如何避免这种浪费呢?你也许会说我检查传入的对象是不是1字节的空类型。且不论这个trait怎么实现,函数指针、捕获一个int的lambda等类型都声称自己是trivial的小对象,也不应该分配到heap中去。

之前说过,std::function 的大小是固定的,但是这个大小是可以自己定的。我们可以在std::function 的类定义中加入一个空白的、大小适中的field,用在存放这些小对象,从而避免这些情况下的动态内存操作。同时,既然有了这片空间,也就不需要看传入的对象是不是1字节的空类型了。

而对于更大的类型,虽然这个field不足以存放函数对象,但足以存放一个指针,这种分时复用的结构可以用union来实现。这种小对象直接存储、大对象在heap上开辟空间并存储指针的方法,称为small object optimization。

在利用继承的实现中,函数对象被包装在一个子类中,std::function 中持有一个其父类的指针。然而为了效率,我们需要把空白field和这个指针union起来。union总给人一种底层的感觉,在不确定这个union到底存储的是什么的时候,当然不能通过其中的指针去调用虚函数。在这样的设计中,多态性不再能用继承体系实现了,我们需要另一种实现多态的方法。


1.3 用函数指针实现多态

回想一下虚函数是如何实现的?带有virtual function的类的对象中会安插vptr,这个指针指向一个vtable,这个vtable含有多个slot,里面含有指向type_info对象的指针与函数指针——对,我们需要函数指针!不知你有没有在C中实现过多态,在没有语言特性的帮助下,比较方便的方法是在struct中直接放函数指针。如果要像C++那样用上vptr和vtable,你得管理好每个类及其对应vtable的内容。你以为这种情况在C++中就有所好转吗?只有你用C++的继承体系,编译器才会帮你做这些事。想要自己建立一个从类型到vptr的映射,恐怕你得改编译器了。

vptr与vtable的意义是什么?其一,每个基类只对应一个vptr,大小固定,多重继承下便于管理,但这点与这篇文章的主题没有关联;其二,当基类有多个虚函数的时候,使用vptr可以节省存储对象的空间,而如果用函数指针的话,虽然少了一次寻址,但继承带来的空间overhead取决于虚函数的数量,由于至少一个,函数指针的占用的空间不会少于vptr,在虚函数数量较多的情况下,函数指针就要占用比较大的空间了。

既然我们已经无法在std::function中使用vptr,我们也应该尽可能减少函数指针的数量,而这又取决于这些函数的功能,进一步取决于std::function类的接口。


1.4std::function的接口

虽然C++标准规定了std::function的接口就应该是这样,我还是想说说它为什么应该是这样。关于其他的一些问题,比如保存值还是保存引用等,可以参考相关资料[4]

最基本的,std::function是一个模板类,模板参数是一个类型(注意是一个类型,不是好几个类型)。我们可以这么写:

std::function f;

f是一个可调用对象,参数为 double,返回值为int。你也许会问,这里既规定了参数类型又规定了返回值类型,怎么就成了一个类型呢?确实是一个类型,int(double)是一个函数类型(注意不是函数指针)。

std::function要包装所有合适类型的对象,就必须有对应的构造函数,所以这是个模板构造函数。参数不是通用引用而是直接传值

template function(F);

可能是为了让编译器对空对象进行优化。同样还有一个模板赋值函数,参数是通用引用。

每个构造函数都有一个添加了std::allocator_arg_t作为第一个参数、内存分配器对象作为第二个参数的版本,C++17中已经移除(GCC从未提供,可能是因为std::function的内存分配无法自定义)。同样删除的还有assign,也是与内存分配器相关的。

另外有一个以std::reference_wrapper作为参数的赋值函数:

template function&operator=(std::reference_wrapper)noexcept;

可以理解为模板赋值函数的特化。没有相应的构造函数。

默认构造函数、nullptr_t构造函数、nullptr_t拷贝赋值函数都将std::function对象置空。当std::function对象没有保存任何函数对象时,operator bool()返回false,与nullptr_t调用operator==会返回true,如果调用将抛出std::bad_function_call异常。

虽然std::function将函数对象包装了起来,但用户还是可以获得原始对象的。target_type()返回函数对象的typeid,target()模板函数当模板参数与函数对象类型相同时返回其指针,否则返回空指针。

作为函数包装器,std::function也是函数对象,可以通过operator()调用,参数按照模板参数中声明的类型传递。

还有一些接口与大部分STL设施相似,有Rule of Five规定的5个方法、swap(),以及std::swap()的特化等。可别小看这个swap() ,它有大用处。

总之,函数对象的复制、移动、赋值、交换等操作都是需要的。对客户来说,除了两个std::function的相等性判定(笔者最近在尝试实现这个)以外,其他能想到的方法它都有。


二、std::function的实现

std::function的实现位于<functional>,后续版本迁移至了<bits/std_function.h>。下面这段代码是GCC 4.8.1(第一个支持完整C++11的版本)中的<functional>头文件,共2579行,默认折叠,慎入。

 functional

这个实现的原理与上面分析的大致相同,使用函数指针实现多态,也使用了small object optimization。

注:标准库的文件的缩进是2格,有时8个空格会用一个tab代替,在将tab显示为4字节的编辑器中缩进会比较乱,我已经把tab全部替换为8个空格;很多人缩进习惯是4格,但如果把2格全部替换成4格也会乱了格式,所以以下摘录自标准库文件的代码全部都是2格缩进。


2.1 类型系统

类型之间的关系,无非是继承、嵌套、组合。这个实现中三者都有。

关于继承,你也许会问,我们刚才不是说了这种实现没法用继承吗?实际上没有矛盾。刚才说的继承,是接口上的继承,讲得更具体点就是要继承虚函数,是一种is-a的关系;而这里的继承,是实现上的继承,是一种is-implemented-in-terms-of的关系,在语言层面大多是private继承。

在泛型编程中,还有一个关于继承的问题,就是在继承体系的哪一层引入模板参数。

嵌套,即类中定义嵌套类型,使类之间的结构更加清晰,在泛型编程中还可以简化设计。

组合,在于一个类的对象中包含其他类的对象,本应属于对象关系的范畴,但是在这个实现中,一个类一般不会在同一个scope内出现多个对象,因此我这里就直接把对象组合的概念拿来用了。

2.1.1 异常类

首先出现的是bad_function_call类型,这是一个异常类,当调用空std::function对象时抛出:

1classbad_function_call :public std::exception2{3public:4virtual~bad_function_call() noexcept;5constchar* what()const noexcept;6};

由于不是模板类(难得能在STL中发现非模板类),实现被编译好放在了目标文件中。虽然GCC开源,但既然这个类不太重要,而且稍微想想就能知道它是怎么实现的了,所以这里就不深究了。

相关的还有一个用于抛出异常的函数:

1void __throw_bad_function_call() __attribute__((__noreturn__));

在<bits/functexcept.h>中。同样只有声明没有定义。

2.1.2 数据存储

有关数据存储的类共有3个:

1class _Undefined_class; 2 3union_Nocopy_types 4{ 5void*      _M_object; 6constvoid* _M_const_object; 7void(*_M_function_pointer)(); 8void(_Undefined_class::*_M_member_pointer)(); 9};1011union_Any_data12{13void*      _M_access()      {return&_M_pod_data[0]; }14constvoid* _M_access()const{return&_M_pod_data[0]; }1516template17_Tp&18    _M_access()19{return*static_cast<_Tp*>(_M_access()); }2021template22const_Tp&23_M_access()const24{return*static_cast(_M_access()); }2526  _Nocopy_types _M_unused;27char_M_pod_data[sizeof(_Nocopy_types)];28};

_Undefined_class,顾名思义,连定义都没有,只是用于声明_Nocopy_types中的成员指针数据域,因为同一个平台上成员指针的大小是相同的。

_Nocopy_types,是4种类型的联合体类型,分别为指针、常量指针、函数指针与成员指针。“nocopy”指的是这几种类型指向的对象类型,而不是本身。

_Any_data,是两种类型的联合体类型,一个是_Nocopy_types,另一个是char数组,两者大小相等。后者是POD的,POD的好处多啊,memcpy可以用,最重要的是复制不会抛异常。非模板_M_access()返回指针,模板_M_access()返回解引用的结果,两者都有const重载。

2.1.3 辅助类

1enum _Manager_operation2{3  __get_type_info,4  __get_functor_ptr,5  __clone_functor,6  __destroy_functor7};

_Manager_operation,枚举类,是前面所说控制std::function的函数指针需要的参数类型。定义了4种操作:获得type_info、获得仿函数(就是函数对象)指针、复制仿函数、销毁(析构)仿函数。从这个定义中可以看出,1.4节所说的各种功能中,需要延迟调用的,除了函数对象调用以外,都可以通过这4个功能来组合起来。我们后面还会进一步探讨这个问题。

1template2struct __is_location_invariant3: integral_constant::value4|| is_member_pointer<_Tp>::value)>5{ };

__is_location_invariant,一个trait类,判断一个类型是不是“位置不变”的。从字面上来理解,一个类型如果是“位置不变”的,那么对于一个这种类型的对象,无论它复制到哪里,各个对象的底层表示都是相同的。在这个定义中,一个类型是“位置不变”的,当且仅当它是一个指针或成员指针(与一般的理解有所不同)。

1template 2struct _Simple_type_wrapper 3  { 4    _Simple_type_wrapper(_Tp __value) : __value(__value) { } 5 6    _Tp __value; 7  }; 8 9template10struct__is_location_invariant<_Simple_type_wrapper<_Tp> >11: __is_location_invariant<_Tp>12{ };

_Simple_type_wrapper,一个简单的包装器,用于避免void*与指针的指针之间类型转换的const问题。以及__is_location_invariant对_Simple_type_wrapper的偏特化。

2.1.4 内存管理基类

类_Function_base定义了一系列用于管理函数对象内存的函数,这是一个非模板类:

1class _Function_base 2{ 3public: 4staticconststd::size_t _M_max_size =sizeof(_Nocopy_types); 5staticconststd::size_t _M_max_align = __alignof__(_Nocopy_types); 6 7template 8class _Base_manager; 910template11class _Ref_manager;1213_Function_base() : _M_manager(0) { }1415~_Function_base()16  {17if (_M_manager)18      _M_manager(_M_functor, _M_functor, __destroy_functor);19  }2021bool_M_empty()const{return!_M_manager; }2223typedefbool(*_Manager_type)(_Any_data&,const_Any_data&,24                                _Manager_operation);2526  _Any_data    _M_functor;27  _Manager_type _M_manager;28};

_Function_base是std::function的实现基类,定义了两个静态常量,用于后面的trait类;两个内部类,用于包装静态方法;函数指针类型_Manager_type的对象_M_manager,用于存取_Any_data类型的_M_functor中的数据;构造函数,将函数指针置为空;析构函数,调用函数指针,销毁函数对象;_M_empty()方法,检测内部是否存有函数对象。

我们来看其中的_Base_manager类:

1template 2class _Base_manager 3  { 4protected: 5staticconstbool__stored_locally = 6(__is_location_invariant<_Functor>::value 7&&sizeof(_Functor) <= _M_max_size 8&& __alignof__(_Functor) <= _M_max_align 9&& (_M_max_align % __alignof__(_Functor) ==0));1011typedef integral_constant _Local_storage;1213static_Functor*14_M_get_pointer(const_Any_data& __source);1516staticvoid17_M_clone(_Any_data& __dest,const_Any_data& __source, true_type);1819staticvoid20_M_clone(_Any_data& __dest,const_Any_data& __source, false_type);2122staticvoid23_M_destroy(_Any_data& __victim, true_type);2425staticvoid26_M_destroy(_Any_data& __victim, false_type);2728public:29staticbool30_M_manager(_Any_data& __dest,const_Any_data& __source,31              _Manager_operation __op);3233staticvoid34_M_init_functor(_Any_data& __functor, _Functor&& __f);3536template37staticbool38_M_not_empty_function(constfunction<_Signature>& __f);3940template41staticbool42_M_not_empty_function(const_Tp*& __fp);4344template45staticbool46_M_not_empty_function(_Tp _Class::*const& __mp);4748template49staticbool50_M_not_empty_function(const_Tp&);5152private:53staticvoid54_M_init_functor(_Any_data& __functor, _Functor&& __f, true_type);5556staticvoid57_M_init_functor(_Any_data& __functor, _Functor&& __f, false_type);58};

定义了一个静态布尔常量__stored_locally,它为真当且仅当__is_location_invarianttrait为真、仿函数放得下、仿函数的align符合两个要求。然后再反过来根据这个值定义trait类_Local_storage(标准库里一般都是根据value trait来生成value)。

其余几个静态方法,顾名思义即可。有个值得思考的问题,

再来看_Ref_manager类:

1template 2class_Ref_manager :public_Base_manager<_Functor*> 3  { 4typedef _Function_base::_Base_manager<_Functor*> _Base; 5 6public: 7staticbool 8_M_manager(_Any_data& __dest,const_Any_data& __source, 9              _Manager_operation __op);1011staticvoid12_M_init_functor(_Any_data& __functor, reference_wrapper<_Functor> __f);13};

_Ref_manager继承自_Base_manager类,覆写了两个静态方法。

2.1.5 仿函数调用

起辅助作用的模板函数__callable_functor:

1template 2inline _Functor& 3__callable_functor(_Functor& __f) 4{return __f; } 5 6template 7inline _Mem_fn<_Member _Class::*> 8__callable_functor(_Member _Class::* &__p) 9{return std::mem_fn(__p); }1011template12inline _Mem_fn<_Member _Class::*>13__callable_functor(_Member _Class::*const&__p)14{return std::mem_fn(__p); }1516template17inline _Mem_fn<_Member _Class::*>18__callable_functor(_Member _Class::*volatile&__p)19{return std::mem_fn(__p); }2021template22inline _Mem_fn<_Member _Class::*>23__callable_functor(_Member _Class::*constvolatile&__p)24{returnstd::mem_fn(__p); }

对非成员指针类型,直接返回参数本身;对成员指针类型,返回mem_fn()的结果(将类对象转换为第一个参数;这个标准库函数的实现不在这篇文章中涉及),并有cv-qualified重载。它改变了调用的形式,把所有的参数都放在了小括号中。

_Function_handler类,管理仿函数调用:

1template 2class _Function_handler; 3 4template 5class_Function_handler<_Res(_ArgTypes...), _Functor> 6:public_Function_base::_Base_manager<_Functor> 7  { 8typedef _Function_base::_Base_manager<_Functor> _Base; 910public:11static _Res12_M_invoke(const_Any_data& __functor, _ArgTypes... __args);13  };1415template16class_Function_handler17:public_Function_base::_Base_manager<_Functor>18  {19typedef _Function_base::_Base_manager<_Functor> _Base;2021public:22staticvoid23_M_invoke(const_Any_data& __functor, _ArgTypes... __args);24  };2526template27class_Function_handler<_Res(_ArgTypes...), reference_wrapper<_Functor> >28:public_Function_base::_Ref_manager<_Functor>29  {30typedef _Function_base::_Ref_manager<_Functor> _Base;3132public:33static _Res34_M_invoke(const_Any_data& __functor, _ArgTypes... __args);35  };3637template38class_Function_handler >39:public_Function_base::_Ref_manager<_Functor>40  {41typedef _Function_base::_Ref_manager<_Functor> _Base;4243public:44staticvoid45_M_invoke(const_Any_data& __functor, _ArgTypes... __args);46  };4748template50class_Function_handler<_Res(_ArgTypes...), _Member _Class::*>51:public_Function_handler52  {53typedef _Function_handler54      _Base;5556public:57static _Res58_M_invoke(const_Any_data& __functor, _ArgTypes... __args);59  };6061template62class_Function_handler63:public_Function_base::_Base_manager<64_Simple_type_wrapper< _Member _Class::* > >65  {66typedef _Member _Class::* _Functor;67typedef _Simple_type_wrapper<_Functor> _Wrapper;68typedef _Function_base::_Base_manager<_Wrapper> _Base;6970public:71staticbool72_M_manager(_Any_data& __dest,const_Any_data& __source,73              _Manager_operation __op);7475staticvoid76_M_invoke(const_Any_data& __functor, _ArgTypes... __args);77};

共有6个特化版本:返回值类型为void、其他;函数对象类型为std::reference_wrapper、成员指针、其他。

继承自_Function_base::_Base_manager或_Function_base::_Ref_manager,提供了静态方法_M_invoke(),用于仿函数调用。有一个覆写的_M_manager(),表面上看是一个偏特化有覆写,实际上是两个,因为返回非void 的成员指针偏特化版本还继承了其对应void 偏特化版本。

2.1.6 接口定义

终于回到伟大的std::function了,但是我们还得再看点别的:

1template 2struct unary_function 3  { 4    typedef _Arg    argument_type;    5 6    typedef _Result    result_type;  7  }; 8 9template10struct binary_function11  {12    typedef _Arg1    first_argument_type; 1314    typedef _Arg2    second_argument_type;1516    typedef _Result    result_type;17};

std::unary_function与std::binary_function,定义了一元和二元函数的参数类型与返回值类型。

1template 2struct _Maybe_unary_or_binary_function { }; 3 4template 5struct_Maybe_unary_or_binary_function<_Res, _T1> 6: std::unary_function<_T1, _Res> { }; 7 8template 9struct_Maybe_unary_or_binary_function<_Res, _T1, _T2>10: std::binary_function<_T1, _T2, _Res> { };

_Maybe_unary_or_binary_function类,当模板参数表示的函数为一元或二元时,分别继承 std::unary_function与std::binary_function。

现在可以给出std::function 类定义与方法声明:

  1template  2class function;  3  4template  5classfunction<_Res(_ArgTypes...)>  6:public_Maybe_unary_or_binary_function<_Res, _ArgTypes...>,  7private _Function_base  8  {  9    typedef _Res _Signature_type(_ArgTypes...); 10 11template 12using_Invoke = decltype(__callable_functor(std::declval<_Functor&>()) 13(std::declval<_ArgTypes>()...) ); 14 15template 16struct _CheckResult 17: is_convertible<_CallRes, _Res1> { }; 18 19template 20struct_CheckResult<_CallRes,void> 21      : true_type { }; 22 23template 24using_Callable = _CheckResult<_Invoke<_Functor>, _Res>; 25 26template 27using_Requires = typename enable_if<_Cond::value, _Tp>::type; 28 29public: 30    typedef _Res result_type; 31 32    function() noexcept; 33 34    function(nullptr_t) noexcept; 35 36function(constfunction& __x); 37 38function(function&& __x); 39 40// TODO: needs allocator_arg_t 41 42template,void>> 44      function(_Functor); 45 46function& 47operator=(constfunction& __x); 48 49function& 50operator=(function&& __x); 51 52function& 53operator=(nullptr_t); 54 55template 56_Requires<_Callable<_Functor>, function&> 57operator=(_Functor&& __f); 58 59template 60function& 61operator=(reference_wrapper<_Functor> __f) noexcept; 62voidswap(function& __x); 63 64// TODO: needs allocator_arg_t 65/* 66    template<typename _Functor, typename _Alloc> 67      void 68      assign(_Functor&& __f, const _Alloc& __a); 69*/ 70 71explicitoperatorbool()const noexcept; 72 73_Resoperator()(_ArgTypes... __args)const; 74 75#ifdef __GXX_RTTI 76consttype_info& target_type()const noexcept; 77 78template      _Functor* target() noexcept; 79 80templateconst_Functor* target()const noexcept; 81#endif 82 83private: 84typedef _Res (*_Invoker_type)(const_Any_data&, _ArgTypes...); 85    _Invoker_type _M_invoker; 86}; 87 88template 89inlinebool 90operator==(constfunction<_Res(_Args...)>& __f, nullptr_t) noexcept; 91 92template 93inlinebool 94operator==(nullptr_t,constfunction<_Res(_Args...)>& __f) noexcept; 95 96template 97inlinebool 98operator!=(constfunction<_Res(_Args...)>& __f, nullptr_t) noexcept; 99100template101inlinebool102operator!=(nullptr_t,constfunction<_Res(_Args...)>& __f) noexcept;103104template105inlinevoid106swap(function<_Res(_Args...)>& __x, function<_Res(_Args...)>& __y);

前面说过,std::function类的模板参数是一个函数类型。一个函数类型也是一个类型;std::function只在模板参数是函数类型时才有意义;因此,有用的std::function是一个特化的模板,需要一个声明。标准库规定没有特化的声明是没有定义的。

std::function继承自两个类:公有继承模板类_Maybe_unary_or_binary_function,私有继承非模板类_Function_base。

前者是公有继承,但实际上没有继承虚函数,不属于接口继承,而是实现继承,继承的是基类定义的类型别名。因为这些类型别名是面向客户的,所以必须公有继承。这个继承使std::function在不同数量的模板参数的实例化下定义不同的类型别名。继承是实现这种功能的唯一方法,SFINAE不行。(这是本文第一次出现SFINAE这个词,我默认你看得懂。这是泛型编程中的常用技巧,如果不会请参考这篇文章或Google。)

后者是私有继承,也属于实现继承,继承了基类的两个数据域与几个静态方法。

_Signature_type是一个类型别名,就是模板参数,是一个函数类型。

_Invoke是一个别名模板,就是仿函数被按参数类型调用的返回类型。如果不能调用,根据SFINAE,S错误不会E,但这个别名只有一个定义,在使用的地方所有S都E了,编译器还是会给E。

_CheckResult是一个trait类,检测第一个模板参数能否转换为第二个。另有第二个参数为void的偏特化,在类型检测上使返回类型为void的std::function对象能支持任何返回值的函数对象。

_Callable也是一个trait类,利用上面两个定义检测仿函数类型与std::function模板参数是否匹配。

_Requires是一个有趣的别名模板,如果模板参数中第一个value trait为true,则定义为第二个模板参数,否则未定义(是没有,不是void),使用时将交给SFINAE处理。它大致上实现了C++20中require关键字的功能。实际上concept在2005年就有proposal了,一路从C++0x拖到C++20。我计划在C++20标准正式发布之前写一篇文章完整介绍concept。

result_type是模板参数函数类型的返回值类型,与基类中定义的相同。

在类定义最后的私有段,还定义了一个函数指针类型以及该类型的一个对象,这是第二个函数指针。

其余的各种函数,在1.4节都介绍过了。

2.1.7 类型关系

讲了这么多类型,你记住它们之间的关系了吗?我们再来自顶向下地梳理一遍。

一个std::function对象中包含一个函数指针,它会被初始化为_Function_handler类中的静态函数的指针。std::function与_Function_handler类之间,可以认为是组合关系。

std::function继承自_Maybe_unary_or_binary_function与_Function_base,两者都是实现继承。

_Function_base中有_Base_manager与_Ref_manager两个嵌套类型,其中后者还继承了前者,并覆写了几个方法。两个类定义了一系列静态方法,继承只是为了避免代码重复。

_Function_base含有两个数据域,一个是函数指针,_Function_base与两个嵌套类型之间既是嵌套又是组合;另一个是_Any_data类型对象,_Function_base与_Any_data之间是组合关系。

而_Any_data是一个联合体,是两部分相同大小数据的联合,分别是char数组和_Nocopy_types类型对象,后者又是4种基本类型的联合。

其余的一些类与函数,都是起辅助作用的。至此,对std::function定义的分析就结束了。


2.2 方法的功能与实现

2.2.1 多态性的体现

之前一直讲,std::function是一个多态的函数对象包装器,其中的难点就在于多态。什么是多态?你能看到这里,水平肯定不低,不知道多态是不可能的。Wikipedia对polymorphism的定义是:In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types.

可以说,我们要在std::function中处理好多态,就是要处理好类型。类型当然不能一个个枚举,但可以分类。这里可以分类的有两处:接口类型,即组成模板参数的类型,以及实现类型,即绑定的仿函数的类型。下面,我们就从这两个角度入手,分析std::function是如何实现的。

2.2.2 本地函数对象

先根据仿函数类型分类,可以在std::function对象内部存储的,无需heap空间的,在这一节讨论。相关的方法有以下3个:

1template 2staticvoid 3_Function_base::_Base_manager<_Functor>:: 4_M_init_functor(_Any_data& __functor, _Functor&& __f, true_type) 5{new (__functor._M_access()) _Functor(std::move(__f)); } 6 7template 8staticvoid 9_Function_base::_Base_manager<_Functor>::10_M_clone(_Any_data& __dest,const_Any_data& __source, true_type)11  {12new(__dest._M_access()) _Functor(__source._M_access<_Functor>());13  }1415template16staticvoid17_Function_base::_Base_manager<_Functor>::18_M_destroy(_Any_data& __victim, true_type)19  {20__victim._M_access<_Functor>().~_Functor();21}

_M_init_functor用于初始化对象,在空白区域上用placementnew移动构造了函数对象。

_M_clone用于复制对象,在目标的空白区域上用placementnew拷贝构造和函数对象。

_M_destroy用于销毁对象,对函数对象显式调用了析构函数。

2.2.3 heap函数对象

然后来看函数对象存储在heap上的情况:

1template 2staticvoid 3_Function_base::_Base_manager<_Functor>:: 4_M_init_functor(_Any_data& __functor, _Functor&& __f, false_type) 5{ __functor._M_access<_Functor*>() =new _Functor(std::move(__f)); } 6 7template 8staticvoid 9_Function_base::_Base_manager<_Functor>::10_M_clone(_Any_data& __dest,const_Any_data& __source, false_type)11  {12__dest._M_access<_Functor*>() =13new_Functor(*__source._M_access<_Functor*>());14  }1516template17staticvoid18_Function_base::_Base_manager<_Functor>::19_M_destroy(_Any_data& __victim, false_type)20  {21delete__victim._M_access<_Functor*>();22}

_M_access<_Functor*>()将空白区域解释为仿函数的指针,并返回其引用,实现了这片区域的分时复用。前两个方法都比前一种情况多一层间接,而销毁方法则直接调用了delete。

2.2.4 两种存储结构如何统一

尽管我们不得不分类讨论,但为了方便使用,还需要一个统一的接口。不知你有没有注意到,上面每一个方法都有一个未命名的参数放在最后,在方法中也没有用到。前一种情况,这个参数都是true_type类型,而后一种都是false_type类型。这个技巧称为tag dispatching,在调用时根据类型特征确定这个位置的参数类型,从而通过重载决定调用哪一个。

1template 2staticvoid 3_Function_base::_Base_manager<_Functor>:: 4_M_init_functor(_Any_data& __functor, _Functor&& __f) 5  { _M_init_functor(__functor, std::move(__f), _Local_storage()); } 6 7template 8staticbool 9_Function_base::_Base_manager<_Functor>::10_M_manager(_Any_data& __dest,const_Any_data& __source,11            _Manager_operation __op)12  {13switch (__op)14      {15  #ifdef __GXX_RTTI16case __get_type_info:17__dest._M_access() = &typeid(_Functor);18break;19#endif20case __get_functor_ptr:21__dest._M_access<_Functor*>() = _M_get_pointer(__source);22break;2324case __clone_functor:25        _M_clone(__dest, __source, _Local_storage());26break;2728case __destroy_functor:29        _M_destroy(__dest, _Local_storage());30break;31      }32returnfalse;33}

这个版本的_M_init_functor()只有两个参数,加上第三个参数委托给重载版本处理,这第三个参数是一个_Local_storage类的对象,它根据__stored_locally而成为true_type与false_type,从而区分开两个重载。

_M_manager()方法,同样地,利用tag dispatching把另两组方法统一起来。它通过第三个枚举类型参数来确定需要的操作。

但是,这个方法的返回值是bool,怎么传出type_info与函数对象指针呢?它们将返回值写入第一个参数所指向的空间中。说起利用参数来传递返回值,我就想起C中的指针、C++中的引用、RVO、Java中的包裹类型、C#中的out关键字……这里的处理方法不仅解决了返回值的问题,同时也使各个操作的参数统一起来。

一个值得思考的问题是为什么不把_M_init_functor()也放到_M_manager()中去?答案是,调用_M_init_functor()的地方在std::function的模板构造或模板赋值函数中,此时是知道仿函数类型的;而其他操作被调用时,主调函数是不知道仿函数类型的,就必须用函数指针存储起来;为了节省空间,就引入一个枚举类_Manager_operation,把几种操作合并到一个函数中。

实际上这一层可以先不统一,就是写两种情况的_M_manager,然后到上一层再统一,但是会增加代码量。

除此以外,还有一种简单的方法将两者统一:

1template 2static_Functor* 3_Function_base::_Base_manager<_Functor>:: 4_M_get_pointer(const_Any_data& __source) 5  { 6const_Functor* __ptr = 7__stored_locally? std::__addressof(__source._M_access<_Functor>()) 8: __source._M_access<_Functor*>(); 9returnconst_cast<_Functor*>(__ptr);10}

三目运算符的条件是一个静态常量,编译器会优化,不浪费程序空间,也不需要在运行时判断,效果与前一种方法相同。至于另外两个方法(指函数)为什么不用这种方法(指将两种情况统一的方法),可能是为了可读性吧。

2.2.5 根据形式区分仿函数类型

在下面一层解决了不同存储结构的问题后,我们还要考虑几种特殊情况。

_M_not_empty_function()用于判断参数是否非空,而不同类型的判定方法是不同的。这里的解决方案很简单,模板方法重载即可。

1template 2template 3staticbool 4_Function_base::_Base_manager<_Functor>:: 5_M_not_empty_function(constfunction<_Signature>& __f) 6{returnstatic_cast(__f); } 7 8template 9template10staticbool11_Function_base::_Base_manager<_Functor>::12_M_not_empty_function(const_Tp*& __fp)13{return __fp; }1415template16template17staticbool18_Function_base::_Base_manager<_Functor>::19_M_not_empty_function(_Tp _Class::*const& __mp)20{return __mp; }2122template23template24staticbool25_Function_base::_Base_manager<_Functor>::26_M_not_empty_function(const_Tp&)27{returntrue; }

在调用时,普通函数对象、std::reference_wrapper对象与成员指针的调用方法是不同的,也需要分类讨论。

1template2static _Res3_Function_handler<_Res(_ArgTypes...), _Functor>::4_M_invoke(const_Any_data& __functor, _ArgTypes... __args)5  {6return(*_Base::_M_get_pointer(__functor))(7std::forward<_ArgTypes>(__args)...);8}

对于普通函数对象,函数调用没什么特殊的。注意自定义operator()必须是const的。

对于std::reference_wrapper对象,由于包装的对象存储为指针,因此存储结构与普通函数对象有所不同,相应地调用也不同。

1template 2staticvoid 3_Function_base::_Ref_manager<_Functor>:: 4_M_init_functor(_Any_data& __functor, reference_wrapper<_Functor> __f) 5  { 6_Base::_M_init_functor(__functor, std::__addressof(__f.get())); 7  } 8 9template10staticbool11_Function_base::_Ref_manager<_Functor>::12_M_manager(_Any_data& __dest,const_Any_data& __source,13            _Manager_operation __op)14  {15switch (__op)16      {17  #ifdef __GXX_RTTI18case __get_type_info:19__dest._M_access() = &typeid(_Functor);20break;21#endif22case __get_functor_ptr:23__dest._M_access<_Functor*>() = *_Base::_M_get_pointer(__source);24returnis_const<_Functor>::value;25break;2627default:28        _Base::_M_manager(__dest, __source, __op);29      }30returnfalse;31  }3233template34static _Res35_Function_handler<_Res(_ArgTypes...), reference_wrapper<_Functor> >::36_M_invoke(const_Any_data& __functor, _ArgTypes... __args)37  {38return__callable_functor(**_Base::_M_get_pointer(__functor))(39std::forward<_ArgTypes>(__args)...);40}

碰到两个星号是不是有点晕?其实只要想,一般情况下存储函数对象的地方现在存储指针,所以要获得原始对象,只需要比一般情况多一次解引用,这样就容易理解了。

对于成员指针,情况又有一点不一样:

1template 2staticbool 3_Function_handler:: 4_M_manager(_Any_data& __dest,const_Any_data& __source, 5            _Manager_operation __op) 6  { 7switch (__op) 8      { 9#ifdef __GXX_RTTI10case __get_type_info:11__dest._M_access() = &typeid(_Functor);12break;13#endif14case __get_functor_ptr:15__dest._M_access<_Functor*>() =16&_Base::_M_get_pointer(__source)->__value;17break;1819default:20        _Base::_M_manager(__dest, __source, __op);21      }22returnfalse;23  }2425template27static _Res28_Function_handler<_Res(_ArgTypes...), _Member _Class::*>::29_M_invoke(const_Any_data& __functor, _ArgTypes... __args)30  {31returnstd::mem_fn(_Base::_M_get_pointer(__functor)->__value)(32std::forward<_ArgTypes>(__args)...);33}

我一直说“成员指针”,而不是“成员函数指针”,是因为数据成员指针也是可以绑定的,这种情况在std::mem_fn()中已经处理好了。

void返回类型的偏特化本应接下来讨论,但之前讲过,这个函数被通过继承复用了。实际上,如果把这里的void改为模板类型,然后交换两个_Function_handler偏特化的继承关系,效果还是一样的,所以就在这里先讨论了。

最后一个需要区分的类型,是返回值类型,属于接口类型。之前都是非void版本,下面还有几个void的偏特化:

1template 2staticvoid 3_Function_handler:: 4_M_invoke(const_Any_data& __functor, _ArgTypes... __args) 5  { 6(*_Base::_M_get_pointer(__functor))( 7std::forward<_ArgTypes>(__args)...); 8  } 910template11staticvoid12_Function_handler >::13_M_invoke(const_Any_data& __functor, _ArgTypes... __args)14  {15__callable_functor(**_Base::_M_get_pointer(__functor))(16std::forward<_ArgTypes>(__args)...);17  }1819template20staticvoid21_Function_handler::22_M_invoke(const_Any_data& __functor, _ArgTypes... __args)23  {24std::mem_fn(_Base::_M_get_pointer(__functor)->__value)(25std::forward<_ArgTypes>(__args)...);26}

void只是删除了return关键字的非void版本,因此void返回类型的std::function对象可以绑定任何返回值的函数对象。

2.2.6 实现组装成接口

我们终于讨论完了各种情况,接下来让我们来见证std::function的大和谐:如何用这些方法组装成std::function 。

  1template  2function<_Res(_ArgTypes...)>::  3  function() noexcept  4  : _Function_base() { }  5  6template  7function<_Res(_ArgTypes...)>::  8  function(nullptr_t) noexcept  9  : _Function_base() { } 10 11template 12function<_Res(_ArgTypes...)>:: 13function(function&& __x) : _Function_base() 14  { 15__x.swap(*this); 16  } 17 18template 19  auto 20function<_Res(_ArgTypes...)>:: 21operator=(constfunction& __x) 22-> function& 23  { 24function(__x).swap(*this); 25return*this; 26  } 27 28template 29  auto 30function<_Res(_ArgTypes...)>:: 31operator=(function&& __x) 32-> function& 33  { 34function(std::move(__x)).swap(*this); 35return*this; 36  } 37 38template 39  auto 40function<_Res(_ArgTypes...)>:: 41operator=(nullptr_t) 42-> function& 43  { 44if (_M_manager) 45      { 46        _M_manager(_M_functor, _M_functor, __destroy_functor); 47_M_manager =0; 48_M_invoker =0; 49      } 50return*this; 51  } 52 53template 54  auto 55function<_Res(_ArgTypes...)>:: 56operator=(_Functor&& __f) 57-> _Requires<_Callable<_Functor>, function&> 58  { 59function(std::forward<_Functor>(__f)).swap(*this); 60return*this; 61  } 62 63template 64template 65    auto 66function<_Res(_ArgTypes...)>:: 67-> function& 68operator=(reference_wrapper<_Functor> __f) noexcept 69    { 70function(__f).swap(*this); 71return*this; 72    } 73 74template 75void 76function<_Res(_ArgTypes...)>:: 77swap(function& __x) 78  { 79    std::swap(_M_functor, __x._M_functor); 80    std::swap(_M_manager, __x._M_manager); 81    std::swap(_M_invoker, __x._M_invoker); 82  } 83 84template 85function<_Res(_ArgTypes...)>:: 86operatorbool()const noexcept 87{return!_M_empty(); } 88 89template 90function<_Res(_ArgTypes...)>:: 91function(constfunction& __x) 92  : _Function_base() 93  { 94if(static_cast(__x)) 95      { 96_M_invoker = __x._M_invoker; 97_M_manager = __x._M_manager; 98        __x._M_manager(_M_functor, __x._M_functor, __clone_functor); 99      }100  }101102template103template104function<_Res(_ArgTypes...)>::105    function(_Functor __f)106    : _Function_base()107    {108typedef _Function_handler<_Signature_type, _Functor> _My_handler;109110if (_My_handler::_M_not_empty_function(__f))111        {112          _My_handler::_M_init_functor(_M_functor, std::move(__f));113_M_invoker = &_My_handler::_M_invoke;114_M_manager = &_My_handler::_M_manager;115        }116    }117118template119  _Res120function<_Res(_ArgTypes...)>::121operator()(_ArgTypes... __args)const122  {123if (_M_empty())124      __throw_bad_function_call();125return_M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);126  }127128template129consttype_info&130function<_Res(_ArgTypes...)>::131target_type()const noexcept132  {133if (_M_manager)134      {135        _Any_data __typeinfo_result;136        _M_manager(__typeinfo_result, _M_functor, __get_type_info);137return*__typeinfo_result._M_access();138      }139else140returntypeid(void);141  }142143template144template145_Functor*146function<_Res(_ArgTypes...)>::147    target() noexcept148    {149if(typeid(_Functor) == target_type() && _M_manager)150        {151          _Any_data __ptr;152if (_M_manager(__ptr, _M_functor, __get_functor_ptr)153&& !is_const<_Functor>::value)154return0;155else156return__ptr._M_access<_Functor*>();157        }158else159return0;160    }161162template163template164const_Functor*165function<_Res(_ArgTypes...)>::166target()const noexcept167    {168if(typeid(_Functor) == target_type() && _M_manager)169        {170          _Any_data __ptr;171          _M_manager(__ptr, _M_functor, __get_functor_ptr);172return__ptr._M_access();173        }174else175return0;176    }177178template179inlinebool180operator==(constfunction<_Res(_Args...)>& __f, nullptr_t) noexcept181{return!static_cast(__f); }182183template184inlinebool185operator==(nullptr_t,constfunction<_Res(_Args...)>& __f) noexcept186{return!static_cast(__f); }187188template189inlinebool190operator!=(constfunction<_Res(_Args...)>& __f, nullptr_t) noexcept191{returnstatic_cast(__f); }192193template194inlinebool195operator!=(nullptr_t,constfunction<_Res(_Args...)>& __f) noexcept196{returnstatic_cast(__f); }197198template199inlinevoid200swap(function<_Res(_Args...)>& __x, function<_Res(_Args...)>& __y)201{ __x.swap(__y); }

我们从swap()开始入手。swap()方法只是简单地将三个数据成员交换了一下,这是正确的,因为它们存储的都是POD类型。我认为,这个实现对函数对象存储在本地的条件的限制太过严格,大小合适的可trivial复制的函数对象也应该可以存储在本地。

在swap()的基础上,拷贝构造、移动构造、拷贝赋值、移动赋值函数很自然地构建起来了,而且只用到了swap()方法。这种技巧称为copy-and-swap。这也就解释了为什么std::function需要那么多延迟调用的操作而表示操作的枚举类只需要定义4种操作。

swap()还可以成为异常安全的基础。由于以上方法只涉及到swap(),而swap()方法是不抛异常的,因此两个移动函数是noexcept的,两个拷贝函数也能保证在栈空间足够时不抛异常,在抛异常时不会出现内存泄漏。

其余的方法,有了前面的基础,看代码就能读懂了。


后记

写这篇文章花了好久呀。这是我第一次写这么长的博客,希望你能有所收获。如果有不懂的地方,可以在评论区留言。如果有任何错误,烦请指正。

我是从实现的角度来写的这篇文章,如果你对其中的一些技巧(SFINAE、tag dispatching)不太熟悉的话,理解起来可能有点困难。相关资料[8]介绍了function类的设计思路,从无到有的构建过程比较容易理解。相关资料[9]分析了另一个版本的std::function实现,可供参考。

文章内容已经很充实了,但是没有图片,不太直观。有空我会加上图片的,这样更容易理解。

另外,在我实现完自己的function类以后,还会对这篇文章作一点补充。自己造一遍轮子,总会有更深刻的感受吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容