1. 概述
std::bind 函数定义在头文件中,是一个函数模板,它就像一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
2. 函数原型
std::bind函数有两种函数原型,定义如下:
| template< class F, class... Args > bind( F&& f, Args&&... args ); template< class R, class F, class... Args > bind( F&& f, Args&&... args );
|
std::bind返回一个基于f的函数对象(obj),调用obj时参数被绑定到args上。f的参数要么被绑定到具体的值,要么被绑定到 placeholders(占位符,如_1, _2, …, _n).
其中占位符_1
,_2
表示,当执行obj函数时的第1,第2 … 第n个参数。
3. 参数及用法解释
3.1 参数
f:一个可调用对象(可以是函数对象、函数指针、函数引用、成员函数指针、数据成员指针),它的参数将被绑定到args上。
args:绑定参数列表,参数会被值或占位符替换,其长度必须与f接收的参数个数一致。
1 2 3 4 5
| 调用可调用对象时,绑定参数被std::move,调用参数被std::forward,你得根据可调用对象的行为来判断std::bind返回的函数对象是否可以多次调用。
绑定参数可以是bind表达式,占位符被替换为外层的调用参数,相当于用调用参数来调用这个bind表达式,求值后用来调用外层bind表达式——我是在读源码读到一半一脸懵逼的时候才知道这件事的。这与可调用对象被std::bind以后可以再std::bind并不冲突,因为bind表达式一个是作为绑定参数,另一个是作为可调用对象。
std::bind有个重载,可以用模板参数指定bind表达式的operator()的返回类型。
|
3.2 调用形式
调用std::bind的一般形式为:
1
| auto newCallable = std::bind(callable, arg_list);
|
其中,newCallable 本身是一个可调用对象,arg_list 是一个逗号分隔的参数列表,对应给定的 callable 的参数。即,当我们调用 newCallable 时,newCallable 会调用callable,并传递给它 arg_list 中的参数。
3.3 返回类型
std::bind 的返回类型是一个函数对象,这个函数对象是一个未指定类型T的函数对象,这个函数对象的 std::is_bind_expression::value == true;这个函数对象包含以下成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 1. 对象成员 一个由std::forward<F>(f)构造而来的std::decay<F>::type类型的对象,一个对象的每一个参数类型都是由std::forward<Arg_i>(arg_i)构造而来的std::decay<Arg_i>::type。简单来说,std::decay<F>::type对象保存了调用std::bind时传递过来的f参数,而若干个std::decay<Arg_i>::type则保存了传递过来的args参数(一个std::decay<Arg_i>::type保存一个args)。 2. 构造函数 如果T的所有对象成员都是可拷贝的,则它自身也是可拷贝的;如果它的所有对象成员都是可移动构造的,则它自身也是可移动构造的。 3. 成员类型 result_type(从C++17开始result_type已经被弃用) ·如果F是函数指针或者成员函数指针,result_type就是F的返回值类型 ·如果F是一个拥有(或者说定义了)result_type的类类型,那么T的result_type就是F::result_type,即使result_type已经在T中被定义过 4. 成员函数 operator() 这是最应该了解的,因为在实际使用过程中,我们调用std::bind得到的返回值就是用来作为函数调用的。 bind的返回值T,假设我们这样调用:g(a1, a2, a3, … ai); 此时g内部保存的std::decay<F>::type类型的对象将被调用, 它将会按照如下的方式来为a1, a2, …, ai 绑定值。 · 如果调用bind时指定的是reference_wrapper<T>类型的,比如在调用bind时使用了std::ref 或者 std::cref来包装args,那么调用g内部的这个对象时,对应参数会以T&类型传入std::decay<F>::type类型的对象. · 如果在创建g时,使用了嵌套的bind,即g = bind(fn, args…)的参数列表args中,存在某个arg:使得std::is_bind_expression<decltype(arg)>::value == true, 那么这个嵌套的bind表达式会被立即调用,其返回值会被传给ret里的_MyFun作为参数(也就是说嵌套的bind返回值会被当做ret调用时的参数), 如果嵌套的bind里用到了占位符placeholder, 这些placeholder将会从ret的调用参数ret(a1, a2, … ai)中对应位置选择. · 如果在创建g时,使用了占位符placeholders, 即 g = bind(fn, arg1, arg2, …, _1, _2, …), (对于_1, _2…, 有std::is_placeholder<T>::value != 0). 那么a1, a2, …, ai会以转发的形式forward<ai>(ai)传递给_MyFun, a1对应_1, a2对应_2, 以此类推. 否则,ret内部保存的args,即上文提到的_Mybargs(bind调用时绑定的参数们)将被以左值的形式传给_MyFun以完成调用,这些参数和g有相同cv限定属性. 如果g(a1, a2, …, ai)中,有哪些ai没有匹配任何的placeholders,比如在调用bind时,placeholder只有_1, 而g(a1, a2, a3), 那么a2, a3就是没有匹配的,没有被匹配的参数将被求值,但是会被丢弃。 如果g被指定为volatile(volatile or const volatile),结果是未定义的。
|
4 类成员函数回调
在c++中,常用的回调函数场景是,在一个类A中,有一个普通成员函数a,在类B中,有一个普通成员函数b,在b中,想要回调函数a,这才是c++回调函数的正确打开方式。
先上一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <iostream> #include <functional> using namespace std; using namespace std::placeholders; typedef std::function<void(int,int)> Fun; class B{ public: void call(int a,Fun f) { f(a,2); } }; class Test{ public: void callback(int a,int b) { cout<<a<<"+"<<b<<"="<<a+b<<endl; } void bind() { Fun fun=std::bind(&Test::callback,this,_1,_2); B b; b.call(1,fun); } }; int main() { Test test; test.bind(); return 0; }
|
上面的程序中,Test类中的bind函数调用B类中的call函数,b中的call函数又反过来回调Test类中的callback函数。记住function和bind都是c++11标准函数,编译的时候要加-std=c++11。
在分析上面程序之前,先介绍一下两个辅助函数,分别是bind函数和function函数,这两个函数之前是boost函数成员,现在加入到c++11标准中,使用更加方便。
bind函数
定义在头文件functional中。可以看成是对一个函数的改造器,可以借助于集合的观点来说(尽管可能没这回事),可以将bind函数看作是返回一个子函数。这个子函数可以是bind绑定的函数的子集,也可以是本身。
一般常用语法是: newFunName=bind(oldFunName,arg_list);
bind函数返回一个新的函数对象。其中bind第一个参数是oldFunName,它是待绑定的函数名,arg_list是oldFunName的参数列表。注意,这个参数列表是旧函数的参数列表,前面提到,返回的是子函数。我们可以随便给子函数定几个参数,但是肯定不能多于bind所绑定的原函数的参数个数。举个例子:
1 2 3 4
| //g是一个有两个参数的可调用对象 auto g=bind(f,a,b,_2,c,_1); //其中f是具有5个参数的函数 //当我们调用g(x,y)时,实际调用的是f(a,b,y,c,x)
|
在这个示例中,我们可能要调用f函数,并传入5个参数,但是我们现在调用g(x,y),只要传入两个参数,同样能达到这个效果。当然,我费这么多事其实肯定不是为了省几个参数,主要还是为了将一个函数转化成一个可以作为回调的函数指针,可以看成是原函数指针的别名。
上面出现的_1,_2是它的占位符,bind最多可以使用9个占位符。这个占位符命名在std的placeholders中,使用时,要使用using std::placeholders.
function函数
function是一个函数对象的容器
。
如function<int(int,int)> fun; fun是一个函数模板,可以接受两个int型参数,并返回一个int型参数。平时可以将它赋值给一个函数指针。
例如上面的回调函数: Fun fun=std::bind(&Test::callback,this,_1,_2);
其中bind用于绑定一个Test类的callback函数,它有两个参数,在这里,因为它是一个类成员函数,中间传入一个this指针,另外两个_1和_2则是它的两个参数。bind返回一个函数指针,将它赋给fun,fun作为一个函数容器,容纳bind函数返回的临时函数指针。 这样就成功的将fun作为一个函数参数的别名,可以用于传给回调函数了。
关于bind和function函数,再举一个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <iostream> #include <functional> using namespace std; typedef std::function<void ()> fp; void g_fun() { cout<<"g_fun()"<<endl; } class A { public: static void A_fun_static() { cout<<"A_fun_static()"<<endl; } void A_fun() { cout<<"A_fun()"<<endl; } void A_fun_int(int i) { cout<<"A_fun_int() "<<i<<endl; } void init() { fp fp1=std::bind(&A::A_fun,this); fp1(); } void init2() { typedef std::function<void (int)> fpi; fpi f=std::bind(&A::A_fun_int,this,std::placeholders::_1); f(5); } }; int main() { fp f2=fp(&g_fun); f2(); fp f1=fp(&A::A_fun_static); f1(); A().init(); A().init2(); return 0; }
|
5 注意
如可调用 (Callable) 中描述,调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针
(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问其成员的对象。
所以在调用类的成员函数的时候首参必须是this
参考
1 2 3 4 5
| https://blog.csdn.net/afei__/article/details/81985937 https://blog.csdn.net/hyp1977/article/details/51784520 https://en.cppreference.com/w/cpp/utility/functional/bind https://www.cnblogs.com/jerry-fuyi/p/12633621.html https://blog.csdn.net/xiexievv/article/details/50517964
|