STL中 function 源码解析

1. function

本文基于 GCC 9.4

function 的作用就是将各种仿函数的调用统一起来;

1.1 类中非静态成员函数指针为什么是16字节

auto cptr = &A::myfunc;			类中非静态成员函数 ,其类型为 void (A::*)(int)
auto rptr = print_num;  		普通函数

对应汇编代码如下所示,可以看出,编译器为 this 指针预留了 8 字节的空间,此时没有绑定 this 指针,因此赋值为 0;

mov     QWORD PTR [rbp-32], OFFSET FLAT:A::myfunc(int)
mov     QWORD PTR [rbp-24], 0

mov     QWORD PTR [rbp-8], OFFSET FLAT:print_num(int, int)

此处也解释了为什么非静态成员函数无法作为 sort 等函数传入的仿函数指针;

1.2 存放对象

为简洁起见,以下代码中均删除了 const 对象或方法;

(1)function 中可以存放 lambda 表达式、重载()的结构体/类、函数指针、类中成员函数等对象,可以存储这些对象的指针,这些对象的调用方式是相似的;

函数指针调用方式 (*func_ptr)(arg);
重载()的结构体/类调用方式 (*object)(arg);

但这些对象的类型是不同的,如何存储这些对象是首要问题,如下 _Nocopy_types 所示,这里采用了 union 的设计方式,并且采用非常技巧的方式;

 union _Nocopy_types
 {
   void*       _M_object;							存放对象指针,例如 lambda 表达式、重载()的结构体/void (*_M_function_pointer)();					存放函数指针
   void (_Undefined_class::*_M_member_pointer)();	指向 类中的成员函数 的指针,注意其大小为 16 字节(静态成员函数指针仍为 8 字节)
 };

 union [[gnu::may_alias]] _Any_data
 {
   void*       _M_access()       { return &_M_pod_data[0]; } 特例化版本,存取union实际指针,可直接为 placement new 提供指针位置
   
   template<typename _Tp>									 函数模板版本,调用上述版本并进行必须的类型转换,可为 new 服务
     _Tp&													 注意:这里以引用形式返回
     _M_access()
     { return *static_cast<_Tp*>(_M_access()); }

   _Nocopy_types _M_unused;
   char _M_pod_data[sizeof(_Nocopy_types)];				为存放函数指针设置,便于获取union地址
 };

1.3 类之间的关系

在这里插入图片描述

_Functor 为模板参数

(1)在 _Base_manager 中

__stored_locally 为 Bool 值,用于判断是否需要在堆上存储,(一般是查看 _Functor 大小是否大于 16,因为其可能是一个类或 bind

若需要,则调用 new _Functor(),并将对象指针存放到 _Any_data 中;(这也是上图为什么使用虚线的原因)

否则,直接存放到 _Any_data

(2)初始化过程

根据 __stored_locally调用对应 _M_init_functor

调用 _M_access 存放对象

(3)调用过程

_M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);

_M_invoker 中,将上述过程转换为

此处 __functor 即为类中的 _M_functor
(*_M_get_pointer(__functor))(std::forward<_ArgTypes>(__args)...);

_M_get_pointer 函数就是获得实际对象指针,从而实现函数调用;

1.4 一些问题思考

(1)在 _M_get_pointer 中局部存储的情况为什么单独处理,为什么不能直接转换为_Functor*

 if _GLIBCXX17_CONSTEXPR (__stored_locally)						 第一种方式
   {
     const _Functor& __f = __source._M_access<_Functor>();		 获取对象本身
     return const_cast<_Functor*>(std::__addressof(__f));
   }
 else // have stored a pointer									
   return __source._M_access<_Functor*>();						 第二种方式

这里的最开始猜测是,若是分配在堆区,那么局部存储的肯定是指针;

而若是局部存储,局部存储的可能是一个对象(例如,一个结构体),

std::function<void(int)> f_display = print_num;					存储函数指针
std::function<void(int)> f_display_obj = PrintNum();			存储结构体

(2)进一步调试

如果存储的是对象,我们想要的其实是 &_M_pod_data[0] ,之后对其解引用就可以获得实际对象,

如果存储的堆区指针,则&_M_pod_data[0] 中存储的为堆区指针,我们需要的是*&_M_pod_data[0]

因此第一种方式获取的是, &_M_pod_data[0],适用于对象;

第二种方式获取的是,*&_M_pod_data[0],适用于堆区指针;

_Functor*&	_M_access(){ 
	return *static_cast<_Functor**>(_M_access()); 
}

auto tmp = static_cast<_Tp*>(myaccess());  此步的转换获取了指向对象的指针
_Tp tmp1 = (*tmp);						   取对象,进行转换,此步相当于获取对象中存储的内容,因此是错误的;

(3)之前一直很疑惑,为什么局部存储普通函数指针的时候,也要使用第一种方式?

之前感觉这种存储方式,最后需要二重取引用,怎么也与当前一重取引用对不上;

后来发现函数指针的调用有两种方式

  void*(*rig)();
  rig = myaccess;
  (*rig)();		
  rig();			这种方式与上述等价

而且从汇编代码来看,这两种方式生成的汇编代码都是一样的

(4)为何此处使用addressof 而不用取地址 & 符号

重载 & 描述符后,取出的地址与 this 指针不一定一致

可参考 https://en.cppreference.com/w/cpp/memory/addressof 中的示例;

1.5 总结

由上述分析来看,本质上来讲,gcc版本的实现中std::function 就是一个 固定大小的 字符数组,若该字符数组能够存放对象,则将其存放到此处,否则,在堆区创建对象,在此处存放对象指针;

1.6 简单模仿其功能

struct Ptr
{
    int* pad; // add pad to show difference between 'this' and 'data'
    int* data;
    Ptr(int* arg) : pad(nullptr), data(arg)
    {
        std::cout << "Ctor this = " << this << '\n';
    }
    Ptr(int* arg, int *arg1) : pad(arg1), data(arg)
    {
        std::cout << "Ctor this = " << this << '\n';
    }
    Ptr(Ptr&& rp) {
        pad = rp.pad;
        data = rp.data;
        rp.pad = nullptr;
        rp.data = nullptr;
        std::cout << "Ctor this = " << this << '\n';
    }
    void myfunc() {
        std::cout << "myfunc " << '\n';
    }
    void operator()() {
        std::cout << "data = " << *data << std::endl;
    }
    // ~Ptr() { delete data; }
};
 

char arr[16];

void* myaccess() {
    std::cout << "xxx" << std::endl;
    return &arr[0];
}

template<typename _Tp>
_Tp& _M_access() {
    auto tmp = static_cast<_Tp*>(myaccess());
    // _Tp tmp1 = (*tmp);
    return *static_cast<_Tp*>(myaccess());
}

int main()
{
    int x = 42;

    int y = 16;
    
    std::cout << &y << '\n';
    
    Ptr p(&x, &y);
    ::new (myaccess()) Ptr(std::move(p));

    Ptr* op1 = _M_access<Ptr*>();                    // 获取的是 pad 存储的内容,即 y 的地址;

    const Ptr& __f = _M_access<Ptr>();

    Ptr* op2 = const_cast<Ptr*>(std::__addressof(__f));

    std::cout << op1 << std::endl;                   // 

    std::cout << op2 << std::endl;

    // --------------------------------------------

    // Ptr p(&x, &y);

    // ::new (myaccess()) Ptr*(std::move(&p));

    // Ptr* op1 = _M_access<Ptr*>();

    // const Ptr& __f = _M_access<Ptr>();

    // Ptr* op2 = const_cast<Ptr*>(std::__addressof(__f));

    // std::cout << &p << std::endl;              // 0x7ffebd392100

    // std::cout << op1 << std::endl;             // x 变量的地址

    // std::cout << op2 << std::endl;  

    // std::cout << "orin " << myaccess() << std::endl;  


    // std::cout << (void *)myaccess << std::endl;  

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值