ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C++11 工程级应用 07:统一万物可调用对象

C++11 工程级应用 07:统一万物可调用对象 导语C 里面能当函数调用的东西五花八门普通函数、仿函数、成员函数指针…… 每种写法调用方式各不相同想要统一保存、延迟执行、做回调总觉得十分别扭。C11 带来的std::function与std::bind就是来解决这份 “杂乱” 的神器把形形色色的可调用对象收拢到一套统一接口下。Bilibili 同步视频C11 工程级应用 07统一万物可调用对象一、先搞懂什么是 C 可调用对象在 C 的世界不止普通函数可以加括号()执行满足下面 4 种类型统统属于可调用对象函数指针重载了operator()的类对象俗称仿函数可以隐式转换为函数指针的类对象类成员函数指针 / 类成员指针。voidfunc(void){}// 仿函数structFoo{voidoperator()(void){}};// 可转换为函数指针的类structBar{usingFuncPtrvoid(*)(void);staticvoidfunc(void){}operatorFuncPtr(void){returnfunc;}};structA{inta;voidmem_func(void){}};intmain(){// 1.函数指针void(*func_ptr)(void)func;func_ptr();// 2.仿函数Foo foo;foo();//3.可转函数指针的对象Bar bar;bar();//4.成员函数指针void(A::*mem_func_ptr)(void)A::mem_func;intA::*mem_obj_ptrA::a;A aa;aa.*mem_obj_ptr123;(aa.*mem_func_ptr)();return0;}痛点吐槽‍虽然它们都支持()调用语法但类型完全不一样想把它们存到同一个变量、作为回调参数传递、实现延迟调用原生写法非常麻烦。C11 给出解决方案std::function包装器 std::bind绑定器抹平各类可调用对象的差异。二、std::function可调用对象的万能包装盒std::function是一个模板类相当于一个函数包装器。⚠️注意它不能直接容纳原始的类成员函数指针成员函数需要配合std::bind才能装入。模板参数填写函数签名返回值(参数列表)之后这个包装器就可以接纳所有匹配签名的普通函数、静态成员函数、仿函数、函数指针。基础使用示例#includeiostream#includefunctionalvoidfunc(void){std::cout__FUNCTION__n;}classFoo{public:staticintfoo_func(inta){std::cout__FUNCTION__(a)-:;returna;}};classBar{public:intoperator()(inta){std::cout__FUNCTION__(a)-:;returna;}};intmain(void){// 包装普通函数std::functionvoid(void)fr1func;fr1();// 包装类静态成员函数std::functionint(int)fr2Foo::foo_func;std::coutfr2(123)n;// 包装仿函数对象Bar bar;fr2bar;std::coutfr2(123)n;return0;}输出func foo_func(123)-:123 operator()(123)-:123典型场景 1实现回调函数std::function非常适合回调场景可以保存调用对象延迟执行替代 C 时代的函数指针能力更强。#includeiostream#includefunctionalclassA{std::functionvoid()callback_;public:A(conststd::functionvoid()f):callback_(f){}voidnotify(void){callback_();// 延迟触发回调}};classFoo{public:voidoperator()(void){std::cout__FUNCTION__ 回调执行n;}};intmain(void){Foo foo;Aaa(foo);aa.notify();return0;}典型场景 2作为函数参数传入把std::function当作形参外部传入不同逻辑函数内部统一调用。#includeiostream#includefunctional// 只有x为偶数的时候执行传入的回调fvoidcall_when_even(intx,conststd::functionvoid(int)f){if(!(x1)){f(x);}}voidoutput(intx){std::coutx ;}intmain(void){for(inti0;i10;i){call_when_even(i,output);}return0;}输出0 2 4 6 8性能小提示std::function会带来少量开销内部可能发生堆内存分配。如果追求极致性能且确定调用对象类型可以优先用模板但业务代码回调场景std::function带来的开发便利远大于微小损耗。三、std::bind灵活绑定器参数玩出花样如果说std::function是盒子那std::bind就是转换器。核心两大能力将可调用对象和参数绑定生成新仿函数部分绑定参数把 N 元函数变成更少参数的函数。std::placeholders::_1、_2… 是占位符。_1代表调用新生成函数时传入的第一个参数_2代表第二个以此类推。简单示例绑定不同逻辑沿用上面偶数回调的例子我们通过 bind 改造输出行为#includeiostream#includefunctionalvoidcall_when_even(intx,conststd::functionvoid(int)f){if(!(x1)){f(x);}}voidoutput(intx){std::coutx ;}voidoutput_add_2(intx){std::coutx2 ;}intmain(void){{autofrstd::bind(output,std::placeholders::_1);for(inti0;i10;i){call_when_even(i,fr);}std::coutn;}{autofrstd::bind(output_add_2,std::placeholders::_1);for(inti0;i10;i){call_when_even(i,fr);}std::coutn;}return0;}输出0 2 4 6 8 2 4 6 8 10小知识点std::bind返回类型是 STL 内部匿名仿函数所以日常写代码直接用auto接住就好不用手写复杂类型返回值也可以直接赋值给std::function。占位符玩参数重排占位符不止填空位还可以调换参数顺序这是老的bind1st/bind2nd做不到的。#includeiostream#includefunctionalvoidoutput(intx,inty){std::coutx,yn;}intmain(void){std::bind(output,1,2)();//1,2std::bind(output,std::placeholders::_1,2)(1);//1,2std::bind(output,2,std::placeholders::_1)(1);//2,1std::bind(output,std::placeholders::_2,std::placeholders::_1)(1,2);//2,1return0;}重点绑定类成员函数 成员变量原生成员函数指针不能直接丢进std::function配合std::bind就可以完美解决。绑定成员函数时第二个参数必须传入对象指针实例地址。#includeiostream#includefunctionalclassA{public:inti_0;voidoutput(intx,inty){std::coutx,yn;}};intmain(void){A a;//绑定成员函数指定对象a_1 _2为调用时传入参数std::functionvoid(int,int)frstd::bind(A::output,a,std::placeholders::_1,std::placeholders::_2);fr(1,2);//绑定成员变量可以读写成员std::functionint(void)fr_istd::bind(A::i_,a);fr_i()123;std::couta.i_n;//输出123return0;}四、对比老方案bind 彻底替代 bind1st /bind2nd C98 时代提供bind1st、bind2nd只能处理二元函数还要记清楚哪个绑定第一个参数、哪个绑定第二个参数很容易写反。举个例子统计容器中元素大于 10、小于 10 的数量。旧写法// 大于10intcountstd::count_if(coll.begin(),coll.end(),std::bind1st(std::lessint(),10));// 小于10intcountstd::count_if(coll.begin(),coll.end(),std::bind2nd(std::lessint(),10));C11std::bind统一写法可读性更高不容易混淆usingstd::placeholders::_1;//大于10intcountstd::count_if(coll.begin(),coll.end(),std::bind(std::lessint(),10,_1));//小于10intcountstd::count_if(coll.begin(),coll.end(),std::bind(std::lessint(),_1,10));✨拓展能力bind 还支持嵌套组合实现更复杂逻辑。例如筛选集合中5 10的元素可以把两个 bind 表达式组合起来这是旧版 bind1st/bind2nd 完全做不到的。五、总结C 可调用对象种类繁多普通函数、仿函数、转换函数指针对象、成员函数指针原生类型不统一回调保存十分麻烦。std::function是可调用对象包装器接收函数签名做模板参数可以统一保存普通函数、仿函数、函数指针适合回调、延迟执行。std::bind绑定器绑定对象与参数支持部分参数绑定、参数重排可以把成员函数 / 成员变量包装成普通可调用对象输出结果可以存入std::function。std::bind全面淘汰老旧的bind1st、bind2nd功能更强更直观。踩坑提醒使用成员函数 bind 时务必传入对象指针对象生命周期要大于 bind 生成的仿函数避免悬空std::function为空时调用会抛出异常使用前可以用if(fr)判断是否有效性能敏感场景留意std::function少量开销热点路径优先模板方案。通过这一套组合拳C11 让回调、函数式编程变得优雅很多也是现代 C 开发中高频使用的工具。
返回列表