跟我学C++中统篇—STL中的bind

一、标准库中的函数绑定

对C++11标准比较熟悉的都知道,标准库中提供了一个函数模板std::bind,用于将可调用对象(函数,仿函数、函数指针、lambda表达式及函数对象等)与一组参数绑定,然后形成一个新的可调用对象,即Callable Object。当后面调用这个可调用对象时,则可以根据实际情况进行相关的处理。std::bind支持对部分参数绑定、参数占位符、成员函数绑定和嵌套绑定,同时有着更好的兼容性。当然,缺点也有,就是有点“重”。
在C++20/23中,标准库中又提供了两个bind函数,std::bind_front和std::bind_back,顾名思义,可以知道是可以绑定可调用对象的前面或后面的N个参数,让绑定操作变得更灵活方便。下面对它们进行对比分析说明。

二、std::bind

其定义如下:

template<class F,class...Args>/* unspecified */bind(F&&f,Args&&...args);(1)(since C++11)(constexpr since C++20)template<class R,class F,class...Args>/* unspecified */bind(F&&f,Args&&...args);(2)(since C++11)(constexpr since C++20)

std::bind是最通用也是最早的一个绑定接口,它的功能最齐全但应用起来也相对复杂的多。一般在处理函数回调、事件控制、消息管理等需要提前进行映射的情况可以根据情况来主动的控制过滤相关的接口。它还可以实现类似partial application的应用,请参看前面相关的文章。
需要注意的是,要清楚占位符如何使用确保其映射的位置的正确性(参看后面的例程);在绑定类成员函数时,要处理好其生存周期,防止出现悬垂引用等异常;绑定中处理引用等需要显式的使用std::ref或std::cref进行控制;最后,需要开发者自行处理好重载的控制。
在C++14以后,其实更推荐开发者使用lambda表达式来替代std::bind(std::function)的应用。

三、std::bind_front和std::bind_back

std::bind_front和std::bind_back,可以认为是对std::bind在某些场景下的快速应用,主打一个简单方便。通过指定前或后几个参数的绑定控制,来实现接口过滤。由于其没有复杂的占位符,更容易为编译器优化。其定义如下:

std::bind_front template<class F,class...Args>constexpr/* unspecified */bind_front(F&&f,Args&&...args);(1)(since C++20)template<autoConstFn,class...Args>constexpr/* unspecified */bind_front(Args&&...args);(2)(since C++26)std::bind_back template<class F,class...Args>constexpr/* unspecified */bind_back(F&&f,Args&&...args);(3)(since C++23)template<autoConstFn,class...Args>constexpr/* unspecified */bind_back(Args&&...args);(4)(since C++26)

注意一下C++26中的用法,可以与下面的例程代码进行对照。C++26中的用法更让开发者容易与模板的开发匹配。
其可能的实现:

//std::bind_frontnamespace detail{//对常量的处理,如T为const,则U也增加const限定符(通过conditional判断后选择是否增加)template<class T,class U>structcopy_const:std::conditional<std::is_const_v<T>,Uconst,U>{};//删除引用//T&&是万能引用,通过引用折叠判断左值引用类型,不清楚可参看相关资料//通过conditional判断返回左值X&或右值 X&&template<class T,class U,class X=typename copy_const<std::remove_reference_t<T>,U>::type>structcopy_value_category:std::conditional<std::is_lvalue_reference_v<T&&>,X&,X&&>{};//删除U的引用后,将T的类型和CV限定符照搬到Utemplate<class T,class U>structtype_forward_like:copy_value_category<T,std::remove_reference_t<U>>{};//辅助模板template<class T,class U>usingtype_forward_like_t=typename type_forward_like<T,U>::type;}//bind_front是一个非类型模板并使用了变参template<autoConstFn,class...Args>constexprautobind_front(Args&&...args){using F=decltype(ConstFn);//指针类型判断ifconstexpr(std::is_pointer_v<F>or std::is_member_pointer_v<F>)static_assert(ConstFn!=nullptr);//返回deducing this(c++23)的lambdareturn[...bound_args(std::forward<Args>(args))]<class Self,class...T>//bound_args:C++20 的 init-capture pack ,将参数完美转发到bound_args...(//参数类型为std::decay_t<Args>this Self&&,T&&...call_args//获取自身的值类别,如左或右值及有无cv限定符)noexcept//异常处理(//参数匹配处理,前面分析过std::is_nothrow_invocable_v<F,detail::type_forward_like_t<Self,std::decay_t<Args>>...,T...>)//invoke_result_t推导保证SFINAE->std::invoke_result_t<F,detail::type_forward_like_t<Self,std::decay_t<Args>>...,T...>{//等同于 C++23中std::forward_like,以目标类型控制表达式的值类别(左值/右值)和 const限定符,实现智能的转发returnstd::invoke(ConstFn,std::forward_like<Self>(bound_args)...,std::forward<T>(call_args)...);};}//std::bind_back:分析可参看上面的bind_frontnamespace detail{/* is the same as above */}template<autoConstFn,class...Args>constexprautobind_back(Args&&...args){using F=decltype(ConstFn);ifconstexpr(std::is_pointer_v<F>or std::is_member_pointer_v<F>)static_assert(ConstFn!=nullptr);return[...bound_args(std::forward<Args>(args))]<class Self,class...T>(this Self&&,T&&...call_args)noexcept(std::is_nothrow_invocable_v<F,detail::type_forward_like_t<Self,T...,std::decay_t<Args>>...>)->std::invoke_result_t<F,detail::type_forward_like_t<Self,T...,std::decay_t<Args>>...>{returnstd::invoke(ConstFn,std::forward<T>(call_args)...,std::forward_like<Self>(bound_args)...);};}

这两个模板接口其实可以理解为bind-partial,偏绑定。分析看上面代码的注释。

四、三者的不同

这三个绑定的在不同的C++标准中推出,做偏绑定的后两者与bind的不同在于:

  1. 占位符的支持
    bind支持占位符,后面两个不支持。且由于占位符的支持与否,引出了bind支持参数的任意重排,而后面两个不支持
  2. 性能优势
    bind比较重,所以在性能上要比小后两者
  3. 简单性
    相对于bind使用的复杂,后二者则相对简单许多,特别是到C++26,更容易为开发者理解和应用
  4. 参数绑定支持
    bind支持任意参数的绑定,而std::bind_front只支持可调用对象参数列表前面的参数绑定支持,std::bind_back只支持可调用对象参数列表后面的参数绑定

五、例程

cppreference上的例程写得很全面简洁。

  1. std::bind的例程:
#include<functional>#include<iostream>#include<memory>#include<random>voidf(intn1,intn2,intn3,constint&n4,intn5){std::cout<<n1<<' '<<n2<<' '<<n3<<' '<<n4<<' '<<n5<<'\n';}intg(intn1){returnn1;}structFoo{voidprint_sum(intn1,intn2){std::cout<<n1+n2<<'\n';}intdata=10;};intmain(){using namespace std::placeholders;// for _1, _2, _3...std::cout<<"1) argument reordering and pass-by-reference: ";intn=7;// (_1 and _2 are from std::placeholders, and represent future// arguments that will be passed to f1)autof1=std::bind(f,_2,42,_1,std::cref(n),n);n=10;f1(1,2,1001);// 1 is bound by _1, 2 is bound by _2, 1001 is unused// makes a call to f(2, 42, 1, n, 7)std::cout<<"2) achieving the same effect using a lambda: ";n=7;autolambda=[&ncref=n,n](autoa,autob,auto/*unused*/){f(b,42,a,ncref,n);};n=10;lambda(1,2,1001);// same as a call to f1(1, 2, 1001)std::cout<<"3) nested bind subexpressions share the placeholders: ";autof2=std::bind(f,_3,std::bind(g,_3),_3,4,5);f2(10,11,12);// makes a call to f(12, g(12), 12, 4, 5);std::cout<<"4) bind a RNG with a distribution: ";std::default_random_engine e;std::uniform_int_distribution<>d(0,10);autornd=std::bind(d,e);// a copy of e is stored in rndfor(intn=0;n<10;++n)std::cout<<rnd()<<' ';std::cout<<'\n';std::cout<<"5) bind to a pointer to member function: ";Foo foo;autof3=std::bind(&Foo::print_sum,&foo,95,_1);f3(5);std::cout<<"6) bind to a mem_fn that is a pointer to member function: ";autoptr_to_print_sum=std::mem_fn(&Foo::print_sum);autof4=std::bind(ptr_to_print_sum,&foo,95,_1);f4(5);std::cout<<"7) bind to a pointer to data member: ";autof5=std::bind(&Foo::data,_1);std::cout<<f5(foo)<<'\n';std::cout<<"8) bind to a mem_fn that is a pointer to data member: ";autoptr_to_data=std::mem_fn(&Foo::data);autof6=std::bind(ptr_to_data,_1);std::cout<<f6(foo)<<'\n';std::cout<<"9) use smart pointers to call members of the referenced objects: ";std::cout<<f6(std::make_shared<Foo>(foo))<<' '<<f6(std::make_unique<Foo>(foo))<<'\n';}
  1. std::bind_front和std::bind_back例程
#include<cassert>#include<functional>intminus(inta,intb){returna-b;}structS{intval;intminus(intarg)constnoexcept{returnval-arg;}};intmain(){autofifty_minus=std::bind_front(minus,50);assert(fifty_minus(3)==47);// equivalent to: minus(50, 3) == 47automember_minus=std::bind_front(&S::minus,S{50});assert(member_minus(3)==47);//: S tmp{50}; tmp.minus(3) == 47// Noexcept-specification is preserved:static_assert(!noexcept(fifty_minus(3)));static_assert(noexcept(member_minus(3)));// Binding of a lambda:autoplus=[](inta,intb){returna+b;};autoforty_plus=std::bind_front(plus,40);assert(forty_plus(7)==47);// equivalent to: plus(40, 7) == 47#if__cpp_lib_bind_front>=202306LC++26中的用法,和上面的定义对照autofifty_minus_cpp26=std::bind_front<minus>(50);assert(fifty_minus_cpp26(3)==47);automember_minus_cpp26=std::bind_front<&S::minus>(S{50});assert(member_minus_cpp26(3)==47);autoforty_plus_cpp26=std::bind_front<plus>(40);assert(forty_plus(7)==47);#endif#if__cpp_lib_bind_back>=202202Lautomadd=[](inta,intb,intc){returna*b+c;};automul_plus_seven=std::bind_back(madd,7);assert(mul_plus_seven(4,10)==47);//: madd(4, 10, 7) == 47#endif#if__cpp_lib_bind_back>=202306Lautomul_plus_seven_cpp26=std::bind_back<madd>(7);assert(mul_plus_seven_cpp26(4,10)==47);#endif}

bind的操作应用本身并没有什么难度,看看代码就明白了。

六、总结

一般来说,如果环境支持高版本的C++标准并且应用不复杂,推荐使用std::bind_front和std::bind_back,如果再复杂可使用lambda表达式,尽量减少或避免std::bind的使用。只在老的标准代码兼容或为特定的复杂场景下再使用。
本文的重点不是对这几个bind模板接口的详细说明,毕竟出现时间已经很长了。主要是对新标准中的几种bind进行整体的综合分析,以便可以更好的对bind操作,特别与前面手动实现partial application有一个对照分析和理解。目标还是掌握“所以然”,以便在模板和元编程开发中能够更加游刃有余。

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

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

相关文章

【好写作AI】别吵了!用AI写的论文,到底算谁的?一个灵魂拷问的终极回答

好写作AI官方网址&#xff1a;https://www.haoxiezuo.cn/一、当答辩委员扶了扶眼镜&#xff0c;问出那个致命问题… “同学&#xff0c;听说你这篇论文用了AI辅助&#xff0c;那么我想问&#xff1a;哪些部分是你的&#xff0c;哪些部分是机器的&#xff1f;”空气突然安静。这…

雷池WAF安装

干净的Ubuntu Server 24 # 下载并运行官方安装脚本 bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/manager.sh)"安装后&#xff0c;可以通过frp暴露到公网上 参考&#xff1a; https://blog.csdn.net/lpfasd123/article/details/156835633 ht…

【好写作AI】毕业季“分身术”:用AI把一天48小时的魔法变成现实

好写作AI官方网址&#xff1a;https://www.haoxiezuo.cn/一、毕业季的你&#xff0c;是否在扮演“时间扭曲者”&#xff1f;早上&#xff1a;修改论文第8稿&#xff0c;发现文献引用格式全乱。下午&#xff1a;奔赴一场重要的实习面试&#xff0c;路上还在背自我介绍。晚上&…

拥抱大模型:深入剖析 ReAct 的核心原理、技术架构及其对 AI 领域的深远影响

在人工智能的演进历程中&#xff0c;大语言模型展现出了令人惊叹的文本生成能力&#xff0c;但其“黑箱”特性也带来了显著挑战——模型经常产生看似合理但实际错误的“幻觉”回答&#xff0c;缺乏透明推理过程&#xff0c;且无法与外部世界交互获取实时信息。ReAct&#xff08…

5 款 AI 写论文哪个好?实测横评!虎贲等考 AI 凭硬核实力 C 位胜出

毕业季来临&#xff0c;“AI 写论文工具怎么选” 的提问刷爆学术社群。市面上的论文 AI 五花八门&#xff0c;但真正能兼顾学术严谨性、数据真实性、全流程适配性的工具却寥寥无几。作为深耕论文写作科普的测评博主&#xff0c;我耗时一周&#xff0c;对虎贲等考 AI、掌桥科研 …

【好写作AI】一次“氪金”,终身受益?这笔毕业季投资到底值不值?

好写作AI官方网址&#xff1a;https://www.haoxiezuo.cn/一、毕业预算&#xff0c;你是否这样分配&#xff1f;聚餐、拍照、毕业旅行&#xff1a;预算拉满&#xff0c;毫不犹豫。买正版软件、知识付费&#xff1a;眉头一皱&#xff0c;开始犹豫。“这钱花得值吗&#xff1f;有没…

从单智能体到多智能体:九种模式教你搭建高效AI应用

想要构建一个智能体应用&#xff0c;最重要的是什么&#xff1f;可能很多人首先会想到要选择一个性能强大的大模型。这个回答没错&#xff0c;毕竟当前的LLM Based Agent哪能缺少LLM的支撑。但事实却是&#xff0c;很多基于先进大模型构建的智能体没能体现出应用效果&#xff0…

虎贲等考 AI:重塑学术写作新范式,全流程智能赋能论文创作

在学术研究的赛道上&#xff0c;论文写作往往是一道横亘在无数科研人与学子面前的难关 —— 选题缺乏方向、文献梳理耗时费力、数据图表制作繁琐、查重降重反复碰壁、答辩准备手忙脚乱…… 这些痛点&#xff0c;曾让无数人在学术之路上步履维艰。 而今&#xff0c;虎贲等考 AI…

从Windows 10上为远程Linux上安装claude code环境

在本地Linux上启动Claude Code&#xff0c;让Claude Code执行下面指令&#xff1a; 先安装sshpass&#xff0c;本地Linux root密码XXXXconnect to remote ubuntu 24 ip 192.168.X.X, user root XXXX, setup 使用清华ubuntu镜像在远程Linux上为用户jiang 安装nvm&#xff0c;然…

Java IO 与 NIO:从 BIO 阻塞陷阱到 NIO 万级并发

文章目录&#x1f3af;&#x1f525; Java IO 与 NIO&#xff1a;从 BIO 阻塞陷阱到 NIO 万级并发&#xff08;实测 10 万 QPS 性能对比&#xff09;&#x1f31f;&#x1f30d; 引言&#xff1a;数字时代的“脉搏”与 IO 性能天花板&#x1f4ca;&#x1f4cb; 第一章&#xf…

新一代开源CRM系统源码功能全览,支持设定年度、季度、月度等多层级业绩指标

温馨提示&#xff1a;文末有资源获取方式 在数字经济时代&#xff0c;企业的销售能力与其数据整合及流程自动化水平紧密相关。一款旨在构建企业数字销售中枢的新一代CRM系统源码正式亮相。它超越了传统的客户信息记录&#xff0c;集成了从市场线索到现金收入的完整销售自动化能…

什么是 MCP?如何在 Spring Boot + LangChain4j 中落地实战?

如果说大模型是大脑&#xff0c;那么 MCP 就是它的神经末梢。它为 AI 提供了与外部工具、资源和服务交互的标准化方式&#xff0c;让 AI 能够访问最新数据、执行复杂操作&#xff0c;并与现有系统无缝集成。 你可以把 MCP 想象成 AI 应用的“USB 接口”。 就像 USB 为电脑连接…

60N02-ASEMI藏在电路里的“效率密码”

编辑&#xff1a;LL60N02-ASEMI藏在电路里的“效率密码”型号&#xff1a;60N02品牌&#xff1a;ASEMI沟道&#xff1a;NPN封装&#xff1a;TO-252漏源电流&#xff1a;60A漏源电压&#xff1a;20VRDS(on):6.0mΩ批号&#xff1a;最新引脚数量&#xff1a;8封装尺寸&#xff1a…

课程论文别再 “凑字数”!虎贲等考 AI:一键解锁高分学术答卷的秘密

选修课论文赶 DDL 熬到凌晨&#xff0c;专业课论文选题跑偏被导师打回&#xff0c;通识课论文文献堆砌得分惨淡…… 相信这是不少学生写课程论文时的真实写照。课程论文看似要求低于毕业论文&#xff0c;实则暗藏 “选题精准、逻辑严谨、格式规范” 三道门槛&#xff0c;稍不注…

顶刊级科研绘图不用愁!虎贲等考 AI 一键解锁论文 “视觉加分密码”

在学术发表内卷加剧的当下&#xff0c;一张规范、精准、美观的科研图表&#xff0c;早已成为论文投稿、项目申报的 “硬核竞争力”。但无数科研人都曾陷入绘图困境&#xff1a;精通实验却不懂设计&#xff0c;用 Origin 画半天的图表被审稿人批 “不专业”&#xff0c;花高价请…

全面解析 Agent Engineering 的十大工程维度:生产级 Agent 系统的炼成之路

技术层面的突破让AI智能体&#xff08;Agent&#xff09;成为当前绝对的AI热点。但随着Agent应用走向真实业务&#xff0c;也逐渐暴露出其工程瓶颈&#xff1a;不确定性、幻觉、不可观测、性能、安全、成本等多方面存在挑战......这些问题不会因为模型更强而自动消失。“打造一…

【Java毕设全套源码+文档】基于springboot的家校互联管理系统设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

【Java毕设全套源码+文档】基于JavaWeb的毕业季旅游一站式定制服务平台的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

为什么有了 LangChain,还需要 Spring AI?

一位学员在最近AI Agent开发工程师岗位的面试中&#xff0c;面试官抛出了一个非常有意思的问题&#xff1a;“为什么有了 LangChain&#xff0c;还需要 Spring AI&#xff1f;”今天&#xff0c;我们就来深度拆解一下&#xff0c;为什么在 Java 开发者和企业级后端架构的视野里…

开题报告总被毙?虎贲等考 AI:三步搞定导师认可的学术蓝图

开题报告改了 N 版仍被导师打回&#xff0c;选题太泛没新意&#xff0c;文献综述像流水账&#xff0c;技术路线混乱看不懂…… 相信这是不少毕业生写开题报告时的崩溃瞬间。作为毕业论文的 “第一道门槛”&#xff0c;开题报告的质量直接决定后续研究能否顺利推进。虎贲等考 AI…