2026/4/17 16:54:29
网站建设
项目流程
网站建设方案的重要性,网页设计源代码,南京调查公司网站,开通微信公众号要钱吗一、标准库中的函数绑定
对C11标准比较熟悉的都知道#xff0c;标准库中提供了一个函数模板std::bind#xff0c;用于将可调用对象#xff08;函数#xff0c;仿函数、函数指针、lambda表达式及函数对象等#xff09;与一组参数绑定#xff0c;然后形成一个新的可调用对象…一、标准库中的函数绑定对C11标准比较熟悉的都知道标准库中提供了一个函数模板std::bind用于将可调用对象函数仿函数、函数指针、lambda表达式及函数对象等与一组参数绑定然后形成一个新的可调用对象即Callable Object。当后面调用这个可调用对象时则可以根据实际情况进行相关的处理。std::bind支持对部分参数绑定、参数占位符、成员函数绑定和嵌套绑定同时有着更好的兼容性。当然缺点也有就是有点“重”。在C20/23中标准库中又提供了两个bind函数std::bind_front和std::bind_back顾名思义可以知道是可以绑定可调用对象的前面或后面的N个参数让绑定操作变得更灵活方便。下面对它们进行对比分析说明。二、std::bind其定义如下templateclass F,class...Args/* unspecified */bind(Ff,Args...args);(1)(since C11)(constexpr since C20)templateclass R,class F,class...Args/* unspecified */bind(Ff,Args...args);(2)(since C11)(constexpr since C20)std::bind是最通用也是最早的一个绑定接口它的功能最齐全但应用起来也相对复杂的多。一般在处理函数回调、事件控制、消息管理等需要提前进行映射的情况可以根据情况来主动的控制过滤相关的接口。它还可以实现类似partial application的应用请参看前面相关的文章。需要注意的是要清楚占位符如何使用确保其映射的位置的正确性参看后面的例程在绑定类成员函数时要处理好其生存周期防止出现悬垂引用等异常;绑定中处理引用等需要显式的使用std::ref或std::cref进行控制;最后需要开发者自行处理好重载的控制。在C14以后其实更推荐开发者使用lambda表达式来替代std::bindstd::function的应用。三、std::bind_front和std::bind_backstd::bind_front和std::bind_back可以认为是对std::bind在某些场景下的快速应用主打一个简单方便。通过指定前或后几个参数的绑定控制来实现接口过滤。由于其没有复杂的占位符更容易为编译器优化。其定义如下std::bind_front templateclass F,class...Argsconstexpr/* unspecified */bind_front(Ff,Args...args);(1)(since C20)templateautoConstFn,class...Argsconstexpr/* unspecified */bind_front(Args...args);(2)(since C26)std::bind_back templateclass F,class...Argsconstexpr/* unspecified */bind_back(Ff,Args...args);(3)(since C23)templateautoConstFn,class...Argsconstexpr/* unspecified */bind_back(Args...args);(4)(since C26)注意一下C26中的用法可以与下面的例程代码进行对照。C26中的用法更让开发者容易与模板的开发匹配。其可能的实现//std::bind_frontnamespace detail{//对常量的处理如T为const则U也增加const限定符通过conditional判断后选择是否增加templateclass T,class Ustructcopy_const:std::conditionalstd::is_const_vT,Uconst,U{};//删除引用//T是万能引用通过引用折叠判断左值引用类型不清楚可参看相关资料//通过conditional判断返回左值X或右值 Xtemplateclass T,class U,class Xtypename copy_conststd::remove_reference_tT,U::typestructcopy_value_category:std::conditionalstd::is_lvalue_reference_vT,X,X{};//删除U的引用后将T的类型和CV限定符照搬到Utemplateclass T,class Ustructtype_forward_like:copy_value_categoryT,std::remove_reference_tU{};//辅助模板templateclass T,class Uusingtype_forward_like_ttypename type_forward_likeT,U::type;}//bind_front是一个非类型模板并使用了变参templateautoConstFn,class...Argsconstexprautobind_front(Args...args){using Fdecltype(ConstFn);//指针类型判断ifconstexpr(std::is_pointer_vFor std::is_member_pointer_vF)static_assert(ConstFn!nullptr);//返回deducing thisc23的lambdareturn[...bound_args(std::forwardArgs(args))]class Self,class...T//bound_argsC20 的 init-capture pack 将参数完美转发到bound_args...(//参数类型为std::decay_tArgsthis Self,T...call_args//获取自身的值类别如左或右值及有无cv限定符)noexcept//异常处理(//参数匹配处理前面分析过std::is_nothrow_invocable_vF,detail::type_forward_like_tSelf,std::decay_tArgs...,T...)//invoke_result_t推导保证SFINAE-std::invoke_result_tF,detail::type_forward_like_tSelf,std::decay_tArgs...,T...{//等同于 C23中std::forward_like以目标类型控制表达式的值类别左值/右值和 const限定符实现智能的转发returnstd::invoke(ConstFn,std::forward_likeSelf(bound_args)...,std::forwardT(call_args)...);};}//std::bind_back:分析可参看上面的bind_frontnamespace detail{/* is the same as above */}templateautoConstFn,class...Argsconstexprautobind_back(Args...args){using Fdecltype(ConstFn);ifconstexpr(std::is_pointer_vFor std::is_member_pointer_vF)static_assert(ConstFn!nullptr);return[...bound_args(std::forwardArgs(args))]class Self,class...T(this Self,T...call_args)noexcept(std::is_nothrow_invocable_vF,detail::type_forward_like_tSelf,T...,std::decay_tArgs...)-std::invoke_result_tF,detail::type_forward_like_tSelf,T...,std::decay_tArgs...{returnstd::invoke(ConstFn,std::forwardT(call_args)...,std::forward_likeSelf(bound_args)...);};}这两个模板接口其实可以理解为bind-partial偏绑定。分析看上面代码的注释。四、三者的不同这三个绑定的在不同的C标准中推出做偏绑定的后两者与bind的不同在于占位符的支持bind支持占位符后面两个不支持。且由于占位符的支持与否引出了bind支持参数的任意重排而后面两个不支持性能优势bind比较重所以在性能上要比小后两者简单性相对于bind使用的复杂后二者则相对简单许多特别是到C26,更容易为开发者理解和应用参数绑定支持bind支持任意参数的绑定而std::bind_front只支持可调用对象参数列表前面的参数绑定支持std::bind_back只支持可调用对象参数列表后面的参数绑定五、例程cppreference上的例程写得很全面简洁。std::bind的例程#includefunctional#includeiostream#includememory#includerandomvoidf(intn1,intn2,intn3,constintn4,intn5){std::coutn1 n2 n3 n4 n5\n;}intg(intn1){returnn1;}structFoo{voidprint_sum(intn1,intn2){std::coutn1n2\n;}intdata10;};intmain(){using namespace std::placeholders;// for _1, _2, _3...std::cout1) argument reordering and pass-by-reference: ;intn7;// (_1 and _2 are from std::placeholders, and represent future// arguments that will be passed to f1)autof1std::bind(f,_2,42,_1,std::cref(n),n);n10;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::cout2) achieving the same effect using a lambda: ;n7;autolambda[ncrefn,n](autoa,autob,auto/*unused*/){f(b,42,a,ncref,n);};n10;lambda(1,2,1001);// same as a call to f1(1, 2, 1001)std::cout3) nested bind subexpressions share the placeholders: ;autof2std::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::cout4) bind a RNG with a distribution: ;std::default_random_engine e;std::uniform_int_distributiond(0,10);autorndstd::bind(d,e);// a copy of e is stored in rndfor(intn0;n10;n)std::coutrnd() ;std::cout\n;std::cout5) bind to a pointer to member function: ;Foo foo;autof3std::bind(Foo::print_sum,foo,95,_1);f3(5);std::cout6) bind to a mem_fn that is a pointer to member function: ;autoptr_to_print_sumstd::mem_fn(Foo::print_sum);autof4std::bind(ptr_to_print_sum,foo,95,_1);f4(5);std::cout7) bind to a pointer to data member: ;autof5std::bind(Foo::data,_1);std::coutf5(foo)\n;std::cout8) bind to a mem_fn that is a pointer to data member: ;autoptr_to_datastd::mem_fn(Foo::data);autof6std::bind(ptr_to_data,_1);std::coutf6(foo)\n;std::cout9) use smart pointers to call members of the referenced objects: ;std::coutf6(std::make_sharedFoo(foo)) f6(std::make_uniqueFoo(foo))\n;}std::bind_front和std::bind_back例程#includecassert#includefunctionalintminus(inta,intb){returna-b;}structS{intval;intminus(intarg)constnoexcept{returnval-arg;}};intmain(){autofifty_minusstd::bind_front(minus,50);assert(fifty_minus(3)47);// equivalent to: minus(50, 3) 47automember_minusstd::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){returnab;};autoforty_plusstd::bind_front(plus,40);assert(forty_plus(7)47);// equivalent to: plus(40, 7) 47#if__cpp_lib_bind_front202306LC26中的用法和上面的定义对照autofifty_minus_cpp26std::bind_frontminus(50);assert(fifty_minus_cpp26(3)47);automember_minus_cpp26std::bind_frontS::minus(S{50});assert(member_minus_cpp26(3)47);autoforty_plus_cpp26std::bind_frontplus(40);assert(forty_plus(7)47);#endif#if__cpp_lib_bind_back202202Lautomadd[](inta,intb,intc){returna*bc;};automul_plus_sevenstd::bind_back(madd,7);assert(mul_plus_seven(4,10)47);//: madd(4, 10, 7) 47#endif#if__cpp_lib_bind_back202306Lautomul_plus_seven_cpp26std::bind_backmadd(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有一个对照分析和理解。目标还是掌握“所以然”以便在模板和元编程开发中能够更加游刃有余。