引入
bind是对C++98标准中函数适配器bind1st/bind2nd的泛化和增强,可以适配任意的可调用对象,包括函数指针、函数引用、成员函数指针、函数对象和lambda表达式
bind远远超过了STL中的函数绑定器bind1st/bind2nd,可以绑定最多9个函数参数,而且对被绑定对象的要求很低,可以在没有result_type内部类型定义的情况下完成对函数对象的绑定。
bind位于名字空间boost,需要包含头文件< boost/bind.hpp>,即:
#include <boost/bind.hpp>
using namespace boost;
工作原理
bind并不是一个单独的类或函数,而是非常庞大的家族,依据绑定的参数个数和要绑定的调用对象类型,总共有数十个不同的重载形式,但它们的名字都叫做bind,编译器会根据具体的绑定代码自动推导要使用的正确实现。
bind的基本形式如下:
template<class R, class F> bind(F, f);
template<class R, class F, class A1> bind(F f, A1 a1);
namespace{
//匿名命名空间
boost::arg<1> _1;
boost::arg<2> _2;
... // 其他6个占位符定义
}
bind接收的第一个参数必须是一个可调用对象f,可以是函数、函数指针、函数对象或者成员函数指针,之后bind接受最多9个参数,参数的数量必须与f的参数数量相等,这些参数将传递给f作为入参。
绑定完成后,bind会返回一个函数对象,它内部保存了f的拷贝,具有operator(),返回值类型被自动推导为f的返回值类型。在发生调用时,这个函数对象将把之前存储的参数转发给f完成调用。
例如,如果有一个函数func,它的形式时:
func(a1, a2);
那么,它将等价于一个具有无参operator()的bind函数对象调用:
bind(func, a1, a2)();
这是bind最简单的形式。bind表达式存储了func和a1、a2的拷贝,产生了一个临时函数对象。因为func接受两个参数,而a1和a2都是实参,因此临时函数对象将具有一个无参的operator()。当operator()调用发生时函数对象把a1、a2的拷贝传递给func,完成真正的函数调用。
bind的真正威力在于它的占位符,它们分别被定义为_1、_2、_3一直到_9,位于一条匿名名字空间。占位符可以取代bind中参数的位置,在发生函数调用时才接收真正的参数。
占位符的名字表示它在调用式中的顺序,而在绑定表达式中没有顺序的要求,_1不一定必须第一个出现,也不一定只出现一次,比如:
bind(func, _2, _1)(a1, a2);
返回一个具有两个参数的函数对象,第一个参数将放在函数func的第二个位置,而第二个参数则放在第一个位置,调用时等价于:
func(a2 , a1);
使用
绑定普通函数
bind可以绑定普通函数,使用函数名或者函数指针,假如我们具有如下的函数定义:
int f(int a, int b){
// 二元函数
return a + b;
}
int g(int a, int b, int c){
//三元函数
return a + b + c;
}
那么bind(f, 1, 2)将返回一个无参调用的函数对象,等价于f(1, 2)。bind(g, 1, 2, 3)同样返回一个无参调用的函数u,等价于g(1, 2, 3)。这两个绑定表达式没有使用占位符,而是给出了全部具体参数,代码:
std::cout << bind(f, 1, 2) <<< "\n";
std::cout << bind(g, 1, 2, 3) <<< "\n";
相当于:
std::cout << f(1, 2) <<< "\n";
std::cout << g(1, 2, 3) <<< "\n";
使用占位符bind可以有更多的编号,这才是它真正应该做的工作,下面列出来一些占位符的用法:
binf(f, _1, 9)(x); // f(x, 9);
bind(f, _1, _2)(x, y); // f(x, y);
bind(f, _2, _1)(x, y); // f(y, x);
bind(f, _1, _1)(x, y); // f(x, x); y参数被忽略
bind(g, _1, 9, _2)(x, y); // g(x, 8, y);
bind(g, _3, _2, _2)(x, y, z); // g(z, y, y);
注意:我们必须在绑定表达式中提供函数要求的所有参数,无论是真实参数还是占位符均可以。
bind同样可以绑定函数指针,用法相同,比如:
typedef decltype(&f) f_type; //函数指针定义
typedef decltype(&g) g_type; //函数指针定义
f_type pf = f;
g_type pg = g;
int x, y, z;
std::cout << bind(pf, _1, 9)(x) << "\n"; // (*pf)(x, 9);
std::cout << bind(pg, _3, _2, _2)(x, y, z) << "\n"; // (*pg)(z, y, y);
绑定成员函数
bind也可以绑定类的成员函数。
类的成员函数不同于普通函数,因为成员函数指针不能直接调用operator(),它必须被绑定到一个对象或者指针,然后才能得到this指针进而调用成员函数。因此bind需要“牺牲”一个占位符的位置,要求用户提供一个类的实例、引用或者指针,通过对象作为第一个参数来调用成员函数(实际上成员函数的第一个[隐含的]参数就是对象指针),即:
bind(&X::func, x, _1, _2, ...);
这意味着使用成员函数是智能最多绑定8个参数。
例如,我们有一个类demo:
struct demo{
int f(int a, int b){
return a + b;
}
};
那么,下面的bind表达式都是成立的:
demo a, &rf = a; //类的实例对象和引用
demo *p = &a; .. 指针
cout << bind(&demo::f, a, _1, 20)(10) << endl;
cout << bind(&demo::f, ra, _2, _1)(10, 20) << endl;
cout << bind(&demo::f, p, _1, _2)(10, 20) << endl;
注意:我们必须在成员函数前加上取地址操作符&,表示这是一个成员函数指针,否则无法通过编译。
bind能够绑定成员函数,这是个非常有用的功能,它可以替代标准库中令人迷惑的mem_func和mem_func_ref绑定器,用来配合标准算法中操作容器的对象。下面的代码使用bind搭配标准算法for_each用来调试容器中所有对象的print()函数:
struct point{
int x, y;
point(int a = 0, int b = 0) : x(a), y(b){
}
void print(){
cout << "(" << x << " , " << y <<")\n";
}
};
vector<point> v(10);
for_each(v.begin, v.end(), bind(&point::print, _1));
bind同样支持绑定虚成员函数,用法与非虚函数相同,虚函数的行为将由实际调用发生时的实例来决定。