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

    文章

    4422

    浏览量

    67850
  • C++
    C++
    +关注

    关注

    22

    文章

    2129

    浏览量

    77377

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

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

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C++ 11 14 17 20内存管理-指针、智能指针和内存池基础与提升

    pan.baidu.com/s/1g64x9D_jp9ufk4uBpQBmvA?pwd=497f  未来 C++ 底层核心:内存管理实战,从指针到内存池全链路进阶 站在2026年的技术潮头,当AI
    的头像 发表于 04-20 15:51 523次阅读

    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语言代码的实现过程,算法的C语言实现过程具有一般性,通过PID算法的C语言
    发表于 01-21 07: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++之间的区别是什么

    区别 1、面向对象编程 (OOP): C语言是一种面向过程的语言,它强调的是通过函数将任务分解为一系列步骤进行执行。 C++C语言的基础上扩展了面向对象的特性,支持类(class)、封装、继承
    发表于 12-11 06:23

    C/C++条件编译

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

    C++程序异常的处理机制

    1、什么是异常处理? 有经验的朋友应该知道,在正常的CC++编程过程中难免会碰到程序不按照原本设计运行的情况。 最常见的有除法分母为零,数组越界,内存分配失效、打开相应文件失败等等。 一个程序
    发表于 12-02 07:12

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

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

    技能+1!如何在树莓派上使用C++控制GPIO?

    在使用树莓派时,你会发现Python和Scratch是许多任务(包括GPIO编程)中最常用的编程语言。但你知道吗,你也可以使用C++进行GPIO编程,而且这样做还有不少好处。借助WiringPi
    的头像 发表于 08-06 15:33 4503次阅读
    技能+1!如何在树莓派上使用<b class='flag-5'>C++</b>控制GPIO?

    C++ 与 Python:树莓派上哪种语言更优?

    Python是树莓派上的首选编程语言,我们的大部分教程都使用它。然而,C++在物联网项目中同样广受欢迎且功能强大。那么,在树莓派项目中选择哪种语言更合适呢?Python因其简洁性、丰富的库和资源而被
    的头像 发表于 07-24 15:32 1109次阅读
    <b class='flag-5'>C++</b> 与 Python:树莓派上哪种语言更优?

    Perforce QAC产品简介:面向C/C++的静态代码分析工具(已通过SO 26262认证)

    Perforce QAC专为C/C++开发者打造,支持多种编码规范、功能安全标准(ISO 26262)等,广泛用于汽车、医疗、嵌入式开发领域,可帮助快速识别关键缺陷、提升代码质量、实现合规交付。
    的头像 发表于 07-10 15:57 1464次阅读
    Perforce QAC产品简介:面向<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>的静态代码分析工具(已通过SO 26262认证)

    主流的 MCU 开发语言为什么是 C 而不是 C++

    在单片机的地界儿里,C语言稳坐中军帐,C++想分杯羹?难喽。咱电子工程师天天跟那针尖大的内存空间较劲,C++那些花里胡哨的玩意儿,在这儿真玩不转。先说内存这道坎儿。您当stm32f4的256kRAM
    的头像 发表于 05-21 10:33 1210次阅读
    主流的 MCU 开发语言为什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?