std::function原理分析及案例分享

一、简介

std::function的实例可以存储、复制和调用任何可复制构造的可调用目标,包括普通函数、成员函数、类对象(重载了operator()的类的对象)、Lambda表达式等。是对C++现有的可调用实体的一种类型安全的包裹(相比而言,函数指针这种可调用实体,是类型不安全的)。
对于std::function的所有实例,其大小是一样的,但是可以包装不同大小的可调用对象。

二、源码分析

1、成员变量

   //std_function.h  line 88
    template<typename _Res, typename... _ArgTypes>
    class function<_Res(_ArgTypes...)>
    : public _Maybe_unary_or_binary_function<_Res, _ArgTypes...>,
      private _Function_base
    {
    	.....
    private:
      using _Invoker_type = _Res (*)(const _Any_data&, _ArgTypes&&...);
      _Invoker_type _M_invoker;
  };

粗略地看,该类继承于_Maybe_unary_or_binary_function 和_Function_base,类成员变量只有 _M_invoker,从 _M_invoker 的定义可以看出这是一个标准的函数指针。

2、operator()

//std_function.h   line 699
  template<typename _Res, typename... _ArgTypes>
    _Res function<_Res(_ArgTypes...)>::
    operator()(_ArgTypes... __args) const
    {
      if (_M_empty())
		__throw_bad_function_call();
      return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);
    }

class function重载了operator()操作符,operator()对函数指针_M_invoker进行调用,_M_invoker的第一个入参为_M_functor(_M_functor为基类_Function_base中的成员变量),类型为_Any_data。可以推测:_M_invoker并不直接管理可调用目标,而是对可调用目标进行调用;而可调用目标是被_Function_base::_M_functor管理的。

为了验证一下上述推测,接下来看一下class function实例在声明时是怎么初始化_M_invoker的。

3、构造函数

构造函数function(_Functor __f)完成了对_M_invoker的初始化。

//std_function.h   line 683
  template<typename _Res, typename... _ArgTypes>
    template<typename _Functor, typename, typename>
      function<_Res(_ArgTypes...)>::function(_Functor __f)
      : _Function_base()
      {
		typedef _Function_handler<_Res(_ArgTypes...), _Functor> _My_handler;
		if (_My_handler::_M_not_empty_function(__f))
		  {
		  	// 通过函数名可以看出_Function_base::_M_functor管理了可调用目标。
		    _My_handler::_M_init_functor(_M_functor, std::move(__f));
		    // class function的_M_invoker被初始化为_My_handler::_M_invoke
		    _M_invoker = &_My_handler::_M_invoke;
		    // _My_handler::_M_invoke中完成对_Function_base::_M_functor管理目标的调用
		    _M_manager = &_My_handler::_M_manager;
		  }
      }

4、_Function_Handler

查看模板类_Function_handler的源码,一共有四个特化版本。
在这里插入图片描述

template<typename _Res, typename _Functor, typename... _ArgTypes>
    class _Function_handler<_Res(_ArgTypes...), _Functor>
    : public _Function_base::_Base_manager<_Functor>
    {
     	typedef _Function_base::_Base_manager<_Functor> _Base;
	    public:
	      static _Res
	      _M_invoke(const _Any_data& __functor, _ArgTypes&&... __args)
	      {
			return (*_Base::_M_get_pointer(__functor))(
		    std::forward<_ArgTypes>(__args)...);
     	 }
    };

四个特化版本均继承自_Function_base::_Base_manager。其中第一个和第二个特化版本存储的可调用目标是普通函数、类实例或者Lambda表达式,两个特化版本的区别为第一个存储的可调用目标有返回值,第二个存储的可调用目标没有返回值;其中第三个和第四个特化版本存储的可调用目标是类的成员函数,区别在于成员函数是否有返回值。每一个特化版本均包含一个静态函数_M_invoke,使用该函数初始化function::_M_invoker,在该函数中完成可调用目标的调用。 可以看到_M_invoke()函数内调用了_Function_base::_Base_manager的方法。而且在3中,我们知道可调用目标是通过_M_init_functor函数存储的,_M_init_functor是_Function_base::_Base_manager的成员函数,因此我们接着分析_Function_base的源代码

5、class _Function_base

//std_function.h  line 150
class _Function_base
{
public:
  static const std::size_t _M_max_size = sizeof(_Nocopy_types);  // 实际为2个指针的大小
  static const std::size_t _M_max_align = __alignof__(_Nocopy_types);
      
  _Function_base() : _M_manager(nullptr) { }

  ~_Function_base()
  {
    if (_M_manager)
      _M_manager(_M_functor, _M_functor, __destroy_functor);
  }

  bool _M_empty() const { return !_M_manager; }

  typedef bool (*_Manager_type)(_Any_data&, const _Any_data&,
          _Manager_operation);

  _Any_data     _M_functor;   // _M_functor接管可调用目标
  _Manager_type _M_manager;
  
  // 含有一个内部模板类_Base_manager
  template<typename _Functor>
  class _Base_manager
  {
    ...... // 源码分析见第6节
  };
};

类型_Any_data如下所示,为联合体。含有两个联合体成员:_M_unused和_M_pod_data,接管的可调用对象存储在中_M_pod_data,并通过四个_M_access()函数将_M_pod_data转换(或者说是还原)为可调用对象返回出去。

//std_function.h  line 104
union [[gnu::may_alias]] _Any_data
{
  void*       _M_access()       { return &_M_pod_data[0]; }
  const void* _M_access() const { return &_M_pod_data[0]; }

  template<typename _Tp>
  _Tp& _M_access()
  { return *static_cast<_Tp*>(_M_access()); }

  template<typename _Tp>
  const _Tp& _M_access() const
  { return *static_cast<const _Tp*>(_M_access()); }

  _Nocopy_types _M_unused;
  char _M_pod_data[sizeof(_Nocopy_types)];
};

_Nocopy_types的定义如下,

//std_function.h   line 94
class _Undefined_class;

union _Nocopy_types
{
  void*       _M_object;          // 接管类实例或者Lambda表达式
  const void* _M_const_object;    // 接管类实例或者Lambda表达式
  void (*_M_function_pointer)();  // 接管普通函数指针
  void (_Undefined_class::*_M_member_pointer)();  // 接管类成员函数指针,其指针大小是16
};

目前为止,我们已经知道了可调用对象存在哪,但是还不知道如何存起来的,因为我们还没看_Base_manager::_M_init_functor的源码。接下来看一下类_Base_manager的源码。

6、class _Base_manager

对_Function_base的操作均是通过其内部模板类_Base_manager进行的,_Base_manager提供了很多接口,源码如下所示

//std_function.h  line 157
template<typename _Functor>
class _Base_manager // 所有成员均为静态的
{
protected:
  // __stored_locally 用来判断可调用目标存储在哪里
  // 决定_M_pod_data是作为存储空间存储接管对象还是作为指针,指向存储了接管对象所在的地址空间
  // 表达式的值主要取决于sizeof(_Functor),_M_max_size为 16 
  static const bool __stored_locally =
                (__is_location_invariant<_Functor>::value
                 && sizeof(_Functor) <= _M_max_size
                 && __alignof__(_Functor) <= _M_max_align
                 && (_M_max_align % __alignof__(_Functor) == 0));

  typedef integral_constant<bool, __stored_locally> _Local_storage;
  
  // 从_Function_base::_M_functor中取出可调用目标实体
  //当重载了运算符 & 之后,要取地址就需要std::addressof 来获取地址了。
  static _Functor* _M_get_pointer(const _Any_data& __source)
  {
    const _Functor* __ptr =
      __stored_locally? std::__addressof(__source._M_access<_Functor>()) 
      /* have stored a pointer */ : __source._M_access<_Functor*>();
    return const_cast<_Functor*>(__ptr);
  }
  
  /*****************_Function_base::_M_functor的初始化********************/
  // 形参__functor接收实参_Function_base::_M_functor
  // 将形参__f存储在实参_Function_base::_M_functor中
  static void _M_init_functor(_Any_data& __functor, _Functor&& __f)
  { _M_init_functor(__functor, std::move(__f), _Local_storage()); }

  // 根据_Local_storage()调用以下两个private函数,_Local_storage()返回__stored_locally的取值
  static void _M_init_functor(_Any_data& __functor, _Functor&& __f, true_type)
  { ::new (__functor._M_access()) _Functor(std::move(__f)); }  // 使用placement new,存储在栈上

  static void _M_init_functor(_Any_data& __functor, _Functor&& __f, false_type)
  { __functor._M_access<_Functor*>() = new _Functor(std::move(__f)); } //new, 存储在堆上

  // 静态成员函数为_M_manager,实际上就是对内存的管理
  // 根据第三个参数来确定执行的功能:获取、克隆、析构
  static bool _M_manager(_Any_data& __dest, const _Any_data& __source, _Manager_operation __op)
  {
      switch (__op)
      {
      case __get_functor_ptr:
          __dest._M_access<_Functor*>() = _M_get_pointer(__source);
          break;
      case __clone_functor:
          _M_clone(__dest, __source, _Local_storage());
          break;
      case __destroy_functor:
          _M_destroy(__dest, _Local_storage());
          break;
      }
      return false;
  }
  
  // Destroying a location-invariant object may still require
  // destruction.
  static void _M_destroy(_Any_data& __victim, true_type)
  {
    // 存在本地的时候使用了placement new,所以需要显示调用析构
    __victim._M_access<_Functor>().~_Functor();
  }

  // Destroying an object located on the heap.
  static void
  _M_destroy(_Any_data& __victim, false_type)
  {
    // 存在堆上的使用的是new,所以成对使用delete
    delete __victim._M_access<_Functor*>();
  }
};

总结

在这里插入图片描述
1、function的函数指针成员变量_M_invoker被初始化为_Function_handler的成员函数_M_invoker;_M_invoker调用了接管的可调用对象;
2、function的基类_Function_base的成员_M_functor真正接管可调用对象,当可调用对象的大小大于两个指针时存储在堆上,_M_functor指向存储位置;
3、_Function_base的内部类_Base_manager提供接口函数。

7、几个问题

  1. sizeof(std::function) = ?
    32
  2. std::function的对象如何判断相等?
    c++11中std::function并没有实现操作符 == 操作符来判断两个std::function对象是否相等,可以使用 target()函数,c++11标准的官方定义就是返回函数指针,但是使用target()方法需要传入类型。但是可以将function对象与nullptr进行比较。
 //line 762
  template<typename _Res, typename... _Args>
    inline bool
    operator==(const function<_Res(_Args...)>& __f, nullptr_t) noexcept
    { return !static_cast<bool>(__f); }

  /// @overload
  template<typename _Res, typename... _Args>
    inline bool
    operator==(nullptr_t, const function<_Res(_Args...)>& __f) noexcept
    { return !static_cast<bool>(__f); }

GDB验证

根据上述分析,当可调用对象的大小大于16字节时,将在堆上分配内存,且_M_pof_data内储存的是该对象的指针;当可调用对象的大小小于16字节时,将直接储存在_M_pof_data的内存区域();

1、绑定Lambda对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、绑定成员函数(16字节)
在这里插入图片描述

3、绑定成员函数及对象指针(24字节)
在这里插入图片描述

4、绑定成员函数及对象(大小不定)
在这里插入图片描述

参考文档

  1. https://zhuanlan.zhihu.com/p/560964284
  2. https://wenku.baidu.com/view/d0f1ce11f211f18583d049649b6648d7c1c7083e.html
  3. https://blog.csdn.net/FairLikeSnow/article/details/122051985?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-122051985-blog-112335892.pc_relevant_layerdownloadsortv1&spm=1001.2101.3001.4242.1&utm_relevant_index=3(可变参数模板)
  4. https://blog.csdn.net/qq_60750110/article/details/125861059 (模板特化)
  5. https://www.cnblogs.com/xzlq/p/9504851.html (placement new)
  6. https://zhuanlan.zhihu.com/p/415639983
  7. http://blog.bitfoc.us/p/525
  8. https://zh.cppreference.com/w/cpp/utility/functional/function
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值