0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

讲解C++ function 技术的实现与具体运用

Linux爱好者 来源:xdesk 作者:xdesk 2021-01-19 17:05 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

【导读】:本文主要讲解 C++ function 技术的实现与具体运用。

std::function是一个函数对象的包装器,std::function的实例可以存储,复制和调用任何可调用的目标,包括:

函数。

lamada表达式。

绑定表达式或其他函数对象。

指向成员函数和指向数据成员的指针。

当std::function对象没有初始化任何实际的可调用元素,调用std::function对象将抛出std::bad_function_call异常。

本文我们来谈一下std::function的实现原理。

1. std::function简介

在讨论其原理的时候,我们来熟悉一下这个东西是怎么使用的,C++标准库详细说明了这个的基本使用http://www.cplusplus.com/reference/functional/function/.

这里我们大概总结一下。

1.1 Member types

result_type 返回类型
argument_type 如果函数对象只有一个参数,那么这个代表参数类型。
first_argument_type 如果函数对象有两个个参数,那么这个代表第一个参数类型。
second_argument_type 如果函数对象有两个个参数,那么这个代表第二个参数类型。
成员类型 说明

1.2 Member functions

constructor 构造函数:constructs a new std::function instance
destructor 析构函数:destroys a std::function instance
operator= 给定义的function对象赋值
operator bool 检查定义的function对象是否包含一个有效的对象
operator() 调用一个对象
成员函数声明 说明

1.3 基本使用

#include #include intfun(inta,intb,intc,intd) { std::cout<< a << std::endl;  std::cout << b << std::endl;  std::cout << c << std::endl;  std::cout << d << std::endl;  return 0; } class CCaller { public:  int operator()(int a, int b, int c, int d)  {   std::cout << a << std::endl;   std::cout << b << std::endl;   std::cout << c << std::endl;   std::cout << d << std::endl;   return 0;  } }; int main() {  CCaller Caller;  std::functionf; f=[](inta,intb,intc,intd)->int { std::cout<< a << std::endl;   std::cout << b << std::endl;   std::cout << c << std::endl;   std::cout << d << std::endl;   return 0;  };  f(1, 2, 3, 4);  f = Caller;  f(10, 20, 30, 40);  f = fun;  f(100, 200, 300, 400);     return 0; }

从上面我们可以发现,std::function可以表示函数,lamada,可调用类对象。

2. std::function实现

在标准库中STL设计为如下:

template struct_Arg_types {//provideargument_type,etc.(sometimes) }; template struct_Arg_types<_Ty1> {//provideargument_type,etc.(sometimes) typedef_Ty1argument_type; }; template struct_Arg_types<_Ty1, _Ty2> {//provideargument_type,etc.(sometimes) typedef_Ty1first_argument_type; typedef_Ty2second_argument_type; }; template class_Func_class :public_Arg_types<_Types...> {//implementfunctiontemplate public: typedef_Retresult_type; typedef_Func_class<_Ret, _Types...>_Myt; typedef_Func_base<_Ret, _Types...>_Ptrt; private: bool_Local()const_NOEXCEPT {//testforlocallystoredcopyofobject return(_Getimpl()==_Getspace()); } union_Storage {//storageforsmallobjects(basic_stringissmall) max_align_t_Dummy1;//formaximumalignment char_Dummy2[_Space_size];//topermitaliasing _Ptrt*_Ptrs[_Num_ptrs];//_Ptrs[_Num_ptrs-1]isreserved }; _Storage_Mystorage; }; template struct_Get_function_impl<_Ret CALL_OPT (_Types...)> {/*determinetypefromargumentlist*/ typedef_Func_class<_Ret, _Types...>type; }; template classfunction :public_Get_function_impl<_Fty>::type {//wrapperforcallableobjects public: typedeffunction<_Fty>_Myt; };

上面的std::function继承关系比较简单,主要使用

union_Storage { //storageforsmallobjects(basic_stringissmall) max_align_t_Dummy1;//formaximumalignment char_Dummy2[_Space_size];//topermitaliasing _Ptrt*_Ptrs[_Num_ptrs];//_Ptrs[_Num_ptrs-1]isreserved };

这个来存储我们设置的可调用对象,我们从std::function的使用过程看一下整个原理。

2.1 函数对象赋值

我们使用的时候一般使用f = Caller;来设置函数对象,我们看下这个的实现过程。

template _Myt&operator=(reference_wrapper<_Fx>_Func)_NOEXCEPT { //assignwrapperholdingreference_wrappertofunctionobject this->_Tidy(); this->_Reset(_Func); return(*this); }

我们看this->_Reset(_Func)这个函数,因为这个才是设置函数可调用对象的东西。

void_Set(_Ptrt*_Ptr)_NOEXCEPT {//storepointertoobject _Mystorage._Ptrs[_Num_ptrs-1]=_Ptr; } void_Reset_impl(_Fx&&_Val,const_Alloc&_Ax, _Myimpl*,_Alimpl&_Al,false_type) {//storecopyof_Valwithallocator,small(locallystored) _Myimpl*_Ptr=static_cast<_Myimpl *>(_Getspace()); _Al.construct(_Ptr,_STDforward<_Fx>(_Val),_Ax); _Set(_Ptr); } template void_Reset_alloc(_Fx&&_Val,const_Alloc&_Ax) {//storecopyof_Valwithallocator if(!_Test_callable(_Val)) {//nullmemberpointer/functionpointer/std::function return;//alreadyempty } typedeftypenamedecay<_Fx>::type_Decayed; typedef_Func_impl<_Decayed, _Alloc, _Ret, _Types...>_Myimpl; _Myimpl*_Ptr=0; typedef_Wrap_alloc<_Alloc>_Alimpl0; typedeftypename_Alimpl0::templaterebind<_Myimpl>::other_Alimpl; _Alimpl_Al(_Ax); _Reset_impl(_STDforward<_Fx>(_Val),_Ax, _Ptr,_Al,_Is_large<_Myimpl>()); } template void_Reset(_Fx&&_Val) { //storecopyof_Val _Reset_alloc(_STDforward<_Fx>(_Val),allocator()); }

这个代码的主要意思就是创建一个_Func_impl<_Decayed, _Alloc, _Ret, _Types...>指针,然后赋值_Mystorage._Ptrs[_Num_ptrs - 1] = _Ptr;。

设置之后,我们看下调用操作怎么完成。

2.2 operator() 的实现

调用操作主要是通过operator()来实现的,我们看下这个的实现过程。

_Ptrt*_Getimpl()const_NOEXCEPT {//getpointertoobject return(_Mystorage._Ptrs[_Num_ptrs-1]); } _Retoperator()(_Types..._Args)const {//callthroughstoredobject if(_Empty()) _Xbad_function_call(); return(_Getimpl()->_Do_call(_STDforward<_Types>(_Args)...)); }

因此,我们是通过_Func_impl<_Decayed, _Alloc, _Ret, _Types...>转发了调用操作_Do_call

2.3 _Func_impl的实现

class_Func_impl :public_Func_base<_Rx, _Types...> {//derivedclassforspecificimplementationtypes public: typedef_Func_impl<_Callable, _Alloc, _Rx, _Types...>_Myt; typedef_Func_base<_Rx, _Types...>_Mybase; typedef_Wrap_alloc<_Alloc>_Myalty0; typedeftypename_Myalty0::templaterebind<_Myt>::other_Myalty; typedefis_nothrow_move_constructible<_Callable>_Nothrow_move; virtual_Rx_Do_call(_Types&&..._Args) {//callwrappedfunction return(_Invoke_ret(_Forced<_Rx>(),_Callee(), _STDforward<_Types>(_Args)...)); } _Compressed_pair<_Alloc, _Callable>_Mypair; };

_Func_impl这个类通过_Do_call来转发函数对象的调用操作。

3. 总结

这里我们看下std::function的结构信息,如下:

从这里我们发现_Storage大小为:

constint_Num_ptrs=6+16/sizeof(void*); constsize_t_Space_size=(_Num_ptrs-1)*sizeof(void*);

_Num_ptrs值为10。

如果我们赋值的对象有成员变量会是什么情况呢?例如如下:

classCCaller { public: intoperator()(inta,intb,intc,intd) { std::cout<< a << std::endl;   std::cout << b << std::endl;   std::cout << c << std::endl;   std::cout << d << std::endl;   return 0;  }  int a = 1;  int b = 10;  int c = 100; }; int main() {  CCaller Caller;  std::functionf; f=Caller; f(10,20,30,40); return0; }

内存结构如下:

由此我们可以发现std::function是利用一个_Compressed_pair<_Alloc, _Callable> _Mypair;拷贝了元素的数据信息。

主要的初始化过程为:

emplate void_Reset_alloc(_Fx&&_Val,const_Alloc&_Ax) {//storecopyof_Valwithallocator if(!_Test_callable(_Val)) {//nullmemberpointer/functionpointer/std::function return;//alreadyempty } typedeftypenamedecay<_Fx>::type_Decayed; typedef_Func_impl<_Decayed, _Alloc, _Ret, _Types...>_Myimpl; _Myimpl*_Ptr=0; typedef_Wrap_alloc<_Alloc>_Alimpl0; typedeftypename_Alimpl0::templaterebind<_Myimpl>::other_Alimpl; _Alimpl_Al(_Ax); _Reset_impl(_STDforward<_Fx>(_Val),_Ax, _Ptr,_Al,_Is_large<_Myimpl>()); }

其中decay<_Fx>::type定义了_Compressed_pair<_Alloc, _Callable> _Mypair;中_Callable的类型,这个声明如下(也就是去掉引用和其他属性信息):

template structdecay {//determinesdecayedversionof_Ty typedeftypenameremove_reference<_Ty>::type_Ty1; typedeftypename_If::value, typenameremove_extent<_Ty1>::type*, typename_If::value, typenameadd_pointer<_Ty1>::type, typenameremove_cv<_Ty1>::type>::type>::typetype; };

至此,我们大致上完成了std::function的原理分析了,希望在后续的使用中,我们能够知道std::function在什么情况下可以使用,以及背后完成的事情。

责任编辑:lq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    4431

    浏览量

    70412
  • C++
    C++
    +关注

    关注

    22

    文章

    2143

    浏览量

    78377

原文标题:C++ std::function 技术浅谈

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    2026年最新C++训练营课程|体系课67期|完成结

    源码剖析加项目驱动,67期C++训练营体系课课程亮点盘点 在C++培训市场上,绝大多数课程停留在语法讲解和简单示例的层面,学员学完之后能够写出运行无误的代码,但面对真实项目中的复杂问题时依然缺乏
    发表于 08-21 18:08

    2025 U3D引擎外部逆向课程E++ C++(二期)

    代码,而是站在进程边界之外,仅通过读取内存与分析数据结构来理解引擎行为,最终输出一个不修改目标内存、仅通过读取实现功能的外部辅助工具。 在技术架构上,课程构建了E++与C++的精密分工哲学。E++被赋予
    发表于 08-21 10:43

    2026年最新C++训练营课程|体系课67期

    淬炼底层内功:C++训练营67期复盘,以体系化实战重塑核心竞争力 (搜jzit。top) 在数字经济全面爆发的当下,底层开发技术正成为驱动企业增长的核心引擎。然而,许多开发者在掌握了零散的技术组件后
    发表于 08-21 10:17

    2025 U3D引擎外部逆向课程E++ C++(二期)视频教程

    进行了深度研究,分析C#脚本转换为C++代码并最终编译执行的过程,帮助开发者突破常规使用层面的限制,获得更深层次的技术掌控力。 在教学理念上,本课程采用“理论讲解-案例分析-实践演练”
    发表于 08-20 10:45

    C++训练营课程|体系课67期,百战C++训练营

    重塑底层思维:2026新版C++训练营67期进阶复盘(搜jzit。top) 随着2026新版C++训练营体系课67期的正式完结,一场旨在帮助开发者跨越技术天花板的硬核淬炼之旅圆满落下帷幕。在
    发表于 08-20 10:14

    2025 U3D引擎外部逆向课程E++ C++(二期)

    2025 U3D引擎外部逆向课程E++ C++二期:汇编、内存、DLL注入完整教学 在2025年的游戏安全与逆向工程领域,针对Unity3D(U3D)引擎的逆向技术正从传统的内部修改向更隐蔽、更稳
    发表于 08-19 17:34

    视频课程分享—升级版,重学C++ ,重构你的C++知识体系

    Modern C++》这本书,用42个具体条款告诉你\"现代C++的正确打开方式\"。 但我认为,比选课和选书更重要的,是改变你的阅读方式。不要像读语法手册一样去读这些材料,而是带着
    发表于 08-14 10:25

    重学C++ ,重构你的C++知识体系(学:97it-top)#重学C++

    C++
    jf_33470487
    发布于 :2026年06月05日 14:52:21

    C++与lua联合编程

    在现代软件工程的庞大架构中,纯粹的 C++ 与动态脚本语言之间的关系,早已演变成一场精密的“商业分工”。C++ 扮演着提供极致性能的“底层重工业”,而 Lua 则充当着实现灵活业务逻辑的“轻量级
    发表于 04-19 16:27

    C++:const 的空间,常量也能占内存?

    ] != BLACK)): return True return False c++语言5g.FsW.dgdfgsg.cnJIWWQc++语言 c++语言5g.I5y.dgdfgsg.cnJIWWQc++
    发表于 04-16 19:19

    keil实现cc++混合编程

    参考touchgfx生成的代码,发现了一个不需要添加--cpp11 参数的解决方法,具体操作如下。 一、创建一个空白的C文件和头文件在头文件中定义c++文件中需要调用的函数,如图所示 二、在
    发表于 01-26 08:58

    C语言与C++的区别及联系

    缺点:性能比面向过程低。 二、具体语言上的区别 1、关键字的不同 C语言有32个关键字;C++有63个关键字。 2、后缀名不同 C源文件后缀.
    发表于 12-24 07:23

    CC++之间的联系

    1、语法兼容性: C++完全兼容C语言的语法,这意味着任何有效的C语言程序都可以直接在C++编译器下编译通过。 2、底层控制: C++
    发表于 12-11 06:51

    C/C++条件编译

    条件编译是一种在编译时根据条件选择性地包含或排除部分代码的处理方法。在 C/C++ 中,条件编译使用预处理指令 #ifdef、#endif、#else 和 #elif 来实现。常用的条件编译指令有
    发表于 12-05 06:21

    C/C++代码静态测试工具Perforce QAC 2025.3的新特性

     Perforce Validate 中 QAC 项目的相对/根路径的支持。C++ 分析也得到了增强,增加了用于检测 C++ 并发问题的新检查,并改进了实体名称和实
    的头像 发表于 10-13 18:11 1008次阅读
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>代码静态测试工具Perforce QAC 2025.3的新特性