boost:bind

引入

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同样支持绑定虚成员函数,用法与非虚函数相同,虚函数的行为将由实际调用发生时的实例来决定。

猜你喜欢

转载自blog.csdn.net/zhizhengguan/article/details/118546596