std function如何消除不同functor的类型和存储差别

news/2025/12/6 20:38:21/文章来源:https://www.cnblogs.com/tsecer/p/19316553

std function如何消除不同functor的类型和存储差别

intro

std::function颇有类似于python这种动态语言的特性:同一个类型可以容纳函数指针,类对象,lambda表达式等不同类型的调用方法。它既有动态语言的运行时灵活,又有静态语言的编译时安全。

更进一步,不同结构初始化的function可以放在同一个数组中。“数组”又是C/C++语言中一个非常强的限制结构:所有的成员都是相同的类型,相同的内存大小。 但是,这些不同结构初始化的function如何做到具有相同的静态类型和内存布局?一个对象初始化的function明显和一个函数初始化的function需要不同的内存大小,它们的底层调用也有不同的逻辑。

内存

内存大小

无论什么结构,本质上都是需要特定大小的存储空间。C语言最基础的数据结构就是char,所以不同类型存储需要的内存统一定义为char类型的数组。只是对于不同的具体类型,这个内存存储的是不同内容。对于函数,这里可能存储的是一个函数指针即可;对于更大的object,这里可能也是指向一个heap上分配的动态对象。而这个具体的内存如何解释,可以在运行时通过函数转换为特定类型即可。

在function中,这个用来占位内存的就是这个_Any_data结构。所有的function对象,无论是使用什么结构初始化的,它的存储都必须要容纳到这个类型结构中。

///@file: gcc\libstdc++-v3\include\bits\std_function.hunion _Nocopy_types{void*       _M_object;const void* _M_const_object;void (*_M_function_pointer)();void (_Undefined_class::*_M_member_pointer)();};union [[gnu::may_alias]] _Any_data{void*       _M_access()       noexcept { return &_M_pod_data[0]; }const void* _M_access() const noexcept { return &_M_pod_data[0]; }template<typename _Tp>_Tp&_M_access() noexcept{ return *static_cast<_Tp*>(_M_access()); }template<typename _Tp>const _Tp&_M_access() const noexcept{ return *static_cast<const _Tp*>(_M_access()); }_Nocopy_types _M_unused;char _M_pod_data[sizeof(_Nocopy_types)];};

local vs heap

很显然,对于object初始化的function,这个大小的_Any_data根本无法容纳如此多的内存;反之,如果单单是一个函数指针,它可以直接放在_Any_data中。至于functor具体是放在local还是都要分配堆内存(并在_Any_data中保存这个堆内存的地址),好在可以在编译时确定。

///@file: libstdc++-v3\include\bits\std_function.htemplate<typename _Functor>class _Base_manager{protected: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));using _Local_storage = integral_constant<bool, __stored_locally>;

然后根据编译时数值不同,决定是使用local还是heap内存。当然,内存释放也需要做相应的处理。

template<typename _Fn>static void_M_init_functor(_Any_data& __functor, _Fn&& __f)noexcept(__and_<_Local_storage,is_nothrow_constructible<_Functor, _Fn>>::value){_M_create(__functor, std::forward<_Fn>(__f), _Local_storage());}// Construct a location-invariant function object that fits within// an _Any_data structure.template<typename _Fn>static void_M_create(_Any_data& __dest, _Fn&& __f, true_type){::new (__dest._M_access()) _Functor(std::forward<_Fn>(__f));}// Construct a function object on the heap and store a pointer.template<typename _Fn>static void_M_create(_Any_data& __dest, _Fn&& __f, false_type){__dest._M_access<_Functor*>()= new _Functor(std::forward<_Fn>(__f));}

函数指针

在内存类型统一之后,这些内存的具体意义肯定需要根据不同的实例化对象来做特定的解释,这个工作就由function中的_M_invoker来完成。它是一个韩式指针,参数是function模板参数指定的函数类型和额外的一个、前面提到的_Any_data类型。

这个_Invoker_type类型的函数指针根据不同的functor类型指向不同的具体函数实现,并对_Any_data结构进行解析之后调用functor的具体函数。

    private:using _Invoker_type = _Res (*)(const _Any_data&, _ArgTypes&&...);_Invoker_type _M_invoker = nullptr;

在实现中,同样是根据functor的类型来生成不同的函数指针。在function的构造函数中,根据入参functor的不同,创建一个_Handler<_Functor>类型,这个类型根据模板参数的不同而实例化不同的类内部静态函数。构造函数把不同模板类中的静态函数复制给类内部的函数指针。

如此一来,不同functor的类型信息本质上是在函数指针指向的函数实现内部中体现的。但是因为function本身保存的是统一的函数指针,所以所有function对象都有相同的编译类型。

/***  @brief Builds a %function that targets a copy of the incoming*  function object.*  @param __f A %function object that is callable with parameters of*  type `ArgTypes...` and returns a value convertible to `Res`.**  The newly-created %function object will target a copy of*  `__f`. If `__f` is `reference_wrapper<F>`, then this function*  object will contain a reference to the function object `__f.get()`.*  If `__f` is a null function pointer, null pointer-to-member, or*  empty `std::function`, the newly-created object will be empty.**  If `__f` is a non-null function pointer or an object of type*  `reference_wrapper<F>`, this function will not throw.*/// _GLIBCXX_RESOLVE_LIB_DEFECTS// 2774. std::function construction vs assignmenttemplate<typename _Functor,typename _Constraints = _Requires<_Callable<_Functor>>>function(_Functor&& __f)noexcept(_Handler<_Functor>::template _S_nothrow_init<_Functor>()): _Function_base(){static_assert(is_copy_constructible<__decay_t<_Functor>>::value,"std::function target must be copy-constructible");static_assert(is_constructible<__decay_t<_Functor>, _Functor>::value,"std::function target must be constructible from the ""constructor argument");using _My_handler = _Handler<_Functor>;if (_My_handler::_M_not_empty_function(__f)){_My_handler::_M_init_functor(_M_functor,std::forward<_Functor>(__f));_M_invoker = &_My_handler::_M_invoke;_M_manager = &_My_handler::_M_manager;}}

wrap up

  • any_data

使用any_data定义为char数组来占位内存,不同类型对内存通过类型相同,指向不同的函数指针来解释内存的具体意义。

  • 模板函数的类型推导

使用语言提供的函数类型推导,可以获得函数参数的类型信息。

  • 函数内利用推导的参数实例化模板类

在函数内部,利用编译器推导出来的参数类型作为模板函数的类型参数,从而让不同的参数具有不同的函数实现(函数行为)。例如,生成不同实现的函数指针。

demo

仿照std::function的思路,我们可以实现类似的、poor man's implementation: 同样可以将不同大小,不同类型的结构放入同一个类型,并且调用相同的接口并转发到具体类的实现。当然,这里的实现比std的实现简陋到了原始的级别。

tsecer@harry: cat FunctionDemo.cpp
#include <iostream>using namespace std;// Like _Any_data in std::function
struct Any
{char pod[sizeof(void*)];
};// Like _Handler in std::function
template<typename T>
struct Handler
{static void Init(Any &a, const T &t){if (sizeof(T) > sizeof(a)){*(T**)&a.pod = new T(t);}else{*((T*)&a.pod) = t;}}static void Disp(const Any &a){if (sizeof(T) > sizeof(a)){cout << **(T**)&a.pod << endl;}else{cout << *(T*)&a.pod << endl;}}
};// Struct Larger than a pointer
struct Large
{const char *m_p1;const char *m_p2;
};
std::ostream& operator<<(std::ostream& os, const Large& obj)
{os << obj.m_p1 << obj.m_p2;return os;
}// Like std::function in std::function
struct Function
{Any m_a;using DispFunc = void(const Any&);DispFunc *m_pDisp;template <typename T>Function(const T t){using H = Handler<T>;H::Init(m_a, t);m_pDisp = H::Disp;}void Disp() const{m_pDisp(m_a);}
};int main()
{// Function different type can be put in same array with same interface, like std::functionFunction a[] = { Large{"hello", "wolrd"}, 12356, "tsecer" };for (const auto &i : a){i.Disp();}
}tsecer@harry: g++ FunctionDemo.cpp
tsecer@harry: ./a.out
hellowolrd
12356
tsecer
tsecer@harry:

outro

了解了大致的实现思路之后,不得不赞叹作者的奇思妙想:通过朴素的char数组和函数指针这些C语言都已经存在的功能,实现了看似不可能的神奇功能。

不得不感慨:“高端的食材往往只需要简单的烹饪方法”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/990527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年靠谱的轮胎品牌哪家好?口碑好的轮胎品牌哪家好?官方精选可靠品牌指南

2025年靠谱的轮胎品牌哪家好?口碑好的轮胎品牌哪家好?官方精选可靠品牌指南在当前汽车消费加速迭代与新能源渗透率持续提升的背景下,“2025年靠谱且口碑好的轮胎品牌哪家好”正迅速跃升为市场与消费者共同聚焦的决策…

2025年什么牌子的轮胎比较好:权威测评优质轮胎排行

2025年什么牌子的轮胎比较好:权威测评优质轮胎排行2025年什么牌子的轮胎比较好,已成为当前汽车后市场与整车配套领域共同聚焦的战略议题。在新能源渗透率持续攀升、全球贸易格局重构、消费升级加速的背景下,消费者对…

权重衰减

在深度学习中,权重衰减(Weight Decay) 是一种常用的正则化技术,用于防止模型过拟合。它通过在损失函数中添加一个正则化项来限制模型的复杂度,从而提高模型的泛化能力。 1. 权重衰减的原理 权重衰减的核心思想是在…

2025年中国前五轮胎品牌:权威TOP10轮胎榜单发布

2025年中国前五轮胎品牌:权威TOP10轮胎榜单发布2025年中国前五轮胎品牌已成为当前汽车后市场与整车配套领域共同聚焦的核心议题,在新能源浪潮重塑产业格局、全球贸易环境波动加剧的背景下,这一排名不仅关乎品牌位次…

完整教程:简析单目相机模型中的针孔模型

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

问界M8更换轮胎推荐:2025年效率提升80%的推荐

问界M8更换轮胎推荐:2025年效率提升80%的推荐在高端MPV市场快速演进的背景下,问界M8更换轮胎推荐正成为商务精英与高端家庭用户决策链条中的关键节点。Mintel长期监测数据显示,伴随MPV从单纯工具属性向商务接待与高…

题解 CF 2173 Div2

题解 CF 2173 Div2原文来自CnBlogs, 作者: young_tea.

faster r cnn中的动量

在动量优化算法中,动量参数(通常用 β 表示)用于控制上一次梯度更新对当前更新的影响程度。动量参数的值在 0 到 1 之间,0.9 是一个常见的选择,它可以帮助优化算法更快地收敛,并且减少震荡。 在 Faster R-CNN 的…

读大话数据结构的总结1

如下知识均来自大话数据结构这本书,作者程杰 算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。 算法具有五个基本特性: 输入、输出、有穷性、确定性和可行性 1.…

车辆ID跟踪与车牌纠正分析

在车辆识别算法优化中,“车辆ID跟踪优化”(如纠正、增量识别)是评估车辆识别算法稳定性的核心指标。面对GB级的海量日志,人工排查无异于大海捞针。 分享一个轻量级 Python 分析工具,实现从日志流读取、关键事件提…

需求的分层

目录一、最核心的:什么是“概念”,什么是“需求”概念 / 方向(Concept)而需求(Requirement)必须满足三件事:1. 有清晰边界(Scope)2. 有可验证条件(Acceptance Criteria)3. 有系统间的规范定义(Contract)二…

12.5 程序员修炼之道:从小工到专家 第7章 在项目开始之前 - GENGAR

本内容聚焦项目启动前的关键准备工作,围绕需求管理、问题解决、决策判断及规范工具使用展开。 在需求管理上,需摒弃 “贪多求全” 的思维,不盲目搜集需求,而是深挖核心需求并打磨,与用户共情思考。制定需求文档时…

1pcs 3pcs是啥

1pcs 3pcs是啥1 pcs / 3 pcs 是制造业、电子业、物流里最常见的英文缩写: ✅ pcs = pieces(件、个) 所以:1 pcs = 1 个 / 1 件3 pcs = 3 个 / 3 件完全就是数量单位,跟中文的“个、只、件”一样。📦 常见用法示…

红旗HS6 PHEV更换轮胎推荐:2025年用户满意度高的方案

红旗HS6 PHEV更换轮胎推荐:2025年用户满意度高的方案为解决红旗HS6 PHEV车主在“红旗HS6 PHEV更换轮胎推荐”上的选择难题——既要化解混动车型自重高导致的承载与湿地制动隐忧,又要满足电车对静谧性的极致追求,还要…

理想L6更换轮胎推荐:2025年销量突破100万的胎压表现

理想L6更换轮胎推荐:2025年销量突破100万的胎压表现为解决用户在“理想L6更换轮胎推荐”上的选择难题,本文将以资深汽车媒体主编与产品技术分析师的视角,整合全球主流汽车媒体(如《AutoBild》、汽车之家等)的公开…

理想L9更换轮胎推荐:2025年超500万用户力荐的组合

理想L9更换轮胎推荐:2025年超500万用户力荐的组合在高端新能源SUV市场快速演进的背景下,理想L9更换轮胎推荐正成为高净值家庭用户与商务精英的核心关切。理想L9以全尺寸空间、高阶智能驾驶与豪华舒适配置构筑“移动的…

小红书玩疯了!Ai像素级拆解提示词+Nano Banan Pro免费使用教程(附Api接入实战)

大家最近有没有在小红书刷到这种像素级拆解人物的图片?真的玩疯了,有拆解二次元、美女穿搭、玩具手办、游戏角色、乐高玩具....真的万物可拆解。究竟是用什么做的?提示词是什么?今天5分钟教会你。 像素拆解用什么模…

2025.12.6日20:13-harsh无情的;粗糙的

当前已使用内存:MB是340 南京4℃ 霾 今日热点如下 LPL全明星,英国首相花7亿救鱼引争议,元婴集结启程探遗迹,顶级F1车队有多烧钱,鸣潮今州杯S8 Day4,F1阿布扎比历年名场面,Netflix将收购华纳兄弟,黄日华,解析LPL转会后阵…

2.2.STM32-新建工程 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

《软件需求》

目录背景和价值参考资料 背景和价值 软件项目中百分之四十至百分之六十的问题都是在需求分析 阶段埋下的“祸根”(L e ffingwell 1997)。可许多组织仍在那些基本的项目功能上采用一些不 合规范的方法,这样导致的后果…