boost:bind 灰太狼 2022-10-13 14:52 10阅读 0赞 # 引入 # 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同样支持绑定虚成员函数,用法与非虚函数相同,虚函数的行为将由实际调用发生时的实例来决定。
还没有评论,来说两句吧...