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;