1 Boost::bind

在介绍bind之前,我们先介绍一下STL中的绑定机制。我们知道在C++标准库中提供了bind1st,bind2nd函数绑定器和fun_ptr,mem_fun等函数适配器用来将函数绑定为一个函数对象。这些函数绑定器和适配器使用起来比较码分,需要根据全局函数还是类的成员函数,是一个参数还是多个参数等作出不同的选择,甚至有时候STL并不能满足我们的要求。boost库中提供的Boost::bind就是为了解决这个问题而设计的。

2 标准库中函数的绑定

我们首先看一下关于函数绑定是如何出现的。首先我们先看一下下面这个例子:

#include <iostream>
#include <vector>
#include <algorithm>
 
void print(int i)
{
    std::cout << i << std::endl;
}
 
int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(2);
 
    std::for_each(v.begin(), v.end(), print);
}

这个例子很简单,就是依次遍历向量中的元素,并将其作为print的参数进行调用。在标准库中std::for_each()算法的第三个参数只能接受一个参数的函数或者函数对象。如果函数有多个参数,则上面的调用方法就行不通了,对应的解决方案是将函数定义为为函数对象,然后重载operator()的方法,最后利用标准库中的bind1st或者bind2nd来绑定重载运算的第一个参数或者第二个参数。
下面这个例子是将一个二元的相减的函数对象绑定为一个一元函数对象的例子,使用的是标准库中的std::bind1st

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
 
class sub :public std::binary_function<int, int, void>
{
public:
    void operator()(int i, int j) const
    {
        std::cout << i - j << std::endl;
    }
};
 
int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(2);
 
    std::for_each(v.begin(), v.end(), std::bind1st(sub(),10));
}

被绑定的二元函数对象必须要继承std::binary_function,关于这个函数的使用可以参考 stl中std::binary_function的使用 。上面使用的是bind1st将10绑定到第一个参数上,即让10减去向量中的元素。如果想让向量减去10则使用bind2nd,关于标准库绑定的其他用法不是本文的重点这里就不过多介绍了。我们来看一下标准库这个用法的缺点,最明显的一个缺点就是需要改写函数为函数对象,因为在我们的应用中有些函数是允许我们进行修改的。另外一个缺点就是绑定的参数位置不同用的绑定方法也不同,达不到形式上的统一。看完下面介绍的boost::bind,你就会发现标准库的功能简直是太简陋了。

3 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;
  ...               //其他7个占位符,共九个
}

bind 接收的 第一个参数 必须是一个 可调用的对象f,包括 函数、函数指针、函数对象和成员函数 ,之后bind 最多接收9个参数,参数数量 必须与 f的参数数量相等 ,这些参数被传递给f作为入参。绑定完成后,bind会 返回一个函数对象 ,它内部 保存了f的拷贝 ,具有 operator(),返回值类型 被自动推到为 f的返回类型 。在发生调用时这个 函数对象 将把之前 存储的参数 转发给f完成调用。例如,有一个函数func,它的形式如下:

func(a1,a2);

那么,他将等价于一个具有无参operator()的bind函数对象调用:

bind(func,a1,a2)();

上面是bind最简单的调用形式,将一个二元函数绑定为一个无参的函数对象。事实上,我们可以将最多9个参数的函数绑定为无参函数对象。另外,结合bind的占位符,可以实现这些参数以任意顺序调用,而不是不用像标准库那样指定1st,2nd.下例给出了带占位符bind的使用方式:

bind(func,a1,_1)(a2);
//等价于 func(a1,a2);
 
bind(func,_2,_1)(a1,a2);        
//等价于 bind(func,a2,a1);
//等价于 func(a2,a1);
 
bind(func._2,_2)(a1,a2);        
//等价于 bind(func,a2,a2);
//等价于 func(a2,a2);

上面第一个例子中将二元函数绑定为一个一元函数对象,并且使用了占位符将一元函数对象的输入参数传递给二元函数的第二个参数。第二个例子和第三个例子分别说明了占位符可以出现在绑定函数的任意位置并且可以重复任意次,只要参数个数一样即可。可以看出占位符的功能非常强大,占位符的使用也是bind函数的核心功能。下面是使用bind来实现前相减的例子:

#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/bind.hpp>
 
void sub(int a,int b)
{
    std::cout<<a-b<<std::endl;
}
 
int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(2);
 
    std::for_each(v.begin(), v.end(), bind(sub,10,_1));
}

可以看到使用bind之后我们无需自己去定义函数对象了,可以保持原有的函数不变,使用bind来将其绑定为一个一元对象。

4 bind的扩展使用

4.1 bind绑定成员函数

bind除了可以将普通的函数绑定为任意元(小于等于9)的函数对象以外,它可以对类的成员函数进行绑定。不过在绑定时必须要在第二个参数指定绑定的是类的哪个对象(以地址的方式传入)。下面是一个使用示例:

#include <iostream>
#include <string>
#include "boost/bind.hpp"
 
class TestClass
{
public:
    TestClass(int id):Id(id){}
 
    void MemFun(const std::string& str)
    {
        std::cout<<"Object Id="<<Id<<" \nString="<<str<<std::endl;
    }
private:
    int Id;
};
 
int main()
{
    TestClass a1(1);
    TestClass& a1Ref = a1;
    TestClass* a1Ptr = &a1;
    TestClass a2(2);
 
    boost::bind(&TestClass::MemFun,&a1,_1)("TestClass string");
    boost::bind(&TestClass::MemFun,&a1Ref,_1)("TestClass string");
    boost::bind(&TestClass::MemFun,a1Ptr,_1)("TestClass string");
 
    boost::bind(&TestClass::MemFun,&a2,"TestClass string")();
}

上面绑定成员函数的方式和绑定普通函数几乎没有任何区别,只需要多指定被绑定的对象即可。另外由于静态方法对于一个类来说是唯一的,所以其绑定方法和普通函数的绑定一样不用指定类的对象,比如如果上面的MemFun是静态方法,我们直接用boost::bind(&TestClass::MemFun,_1)("string")调用即可。

4.2 复制绑定和非复制绑定

前面我们在绑定成员函数时,指定对象用的是传递地址的方法。在传递地址的过程中没有发生复制操作。事实上除了指定对象地址以外我们还可以指定对象本身,而此时会发生多次的赋值操作。为了说明这一过程情况下面的实例:

#include <iostream>
#include <string>
#include "boost/bind.hpp"
 
class TestClass
{
public:
    TestClass(int id):Id(id){}
 
    void MemFun(const std::string& str)
    {
        std::cout<<"Object Id="<<Id<<" \nString="<<str<<std::endl;
    }
 
    TestClass(const TestClass& other)
    {
        std::cout<<"copy"<<std::endl;
        Id = other.Id;
    }
private:
    int Id;
};
 
int main()
{
    TestClass a1(1);
 
    boost::bind(&TestClass::MemFun,&a1,_1)("TestClass string");
}

这个例子和上面那个例子的使用是一样的,只是为了方便观察复制过程,这里自定义了复制操作。上面程序执行结果为:

Object Id=1
String=TestClass string

接下来我们将上面例子最后绑定过程改为boost::bind(&TestClass::MemFun,a1,_1)("TestClass string");,即传递对象本身。执行结果为:

copy
copy
copy
copy
copy
copy
Object Id=1
String=TestClass string

可以看到当我们传递对象本身时,对象被复制了6次。我们也可以借助boost::ref()来传递对象的引用,即boost::bind(&TestClass::MemFun,boost::ref(a1),_1)("TestClass string");这句话的执行结果和传递指针是一样的。boost::ref()是传递引用的意思,对应的还有boost::cref()用来传递const引用。我们可以在传递对象的引用时使用,下面是另外一个使用示例:

#include <iostream>
#include <vector>
#include <boost/bind.hpp>
#include <algorithm>
 
void add(int i,int j,std::ostream &os)
{
    os<<i+j<<std::endl;
}
 
int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
 
    std::_For_each(v.begin(),v.end(),boost::bind(add,10,_1,boost::ref(std::cout)));
}

4.3 bind绑定成员变量

处理绑定public成员函数以外,bind也可以绑定public成员变量。成员变量的绑定和成员函数是一样的。成员变量绑定为函数对象以后,调用该函数对象相当于打印该变量,下面是一个使用示例:

#include <iostream>
#include <map>
#include <string>
#include <boost/bind.hpp>
 
int main()
{
    typedef std::pair<int,std::string> pair_t;
    pair_t p(123,"myString");
    std::cout<<boost::bind(&pair_t::first,&p)()<<std::endl;
    std::cout<<boost::bind(&pair_t::second,&p)()<<std::endl;
}

4.4 bind绑定函数对象

除了绑定普通函数和成员函数以外,bind还可以绑定函数对象。在绑定函数对象的过程中,如果函数对象 有内部定义的result_type ,那么bind可以自动推到出返回值类型,用法和普通函数一致。但如果函数对象 没有定义result_type ,则需要通过 模板参数手动指定返回类型 ,下面是一个使用示例:

#include <iostream>
#include <boost/bind.hpp>
#include <functional>
 
struct myFun
{
    int operator ()(int a,int b)
    {
        return a+b;
    }
};
 
int main()
{
    std::cout<<boost::bind(std::greater<int>(),_1,10)(5)<<std::endl;
    std::cout<<boost::bind<int>(myFun(),_1,3)(8)<<std::endl;
}

在这个示例中,由于标准库中的greater函数已经定义了result_type,所以我们可78用不用指定函数的返回类型,注意greater的模板参数和bind的模板参数并不是一回事。对于第二个绑定过程,由于我们自定义的函数对象并没有定义定义result_type所有必须要手动的在模板中指定函数返回值类型。

4.5 绑定非标准函数

处理上面提到的函数以外,我们也可以对非标准函数进行绑定。典型的例子就是C中的可变参数函数printf().下面是其绑定的一个实例:

#include <iostream>
#include <boost/bind.hpp>
 
int main()
{
 
    boost::bind<int>(printf,"%d+1=%d\n",_1,_2)(6,7);
}

由于printf的返回类型为int所有我们必须要在bind的模板参数中指定返回类型。

参考文章:

关于bind的使用可以参考:
第3章 函数对象
关于bind和标准库适配器、绑定器的区别,可以参考:
Boost::bind使用详解
绑定成员函数(指针,引用,普通对象,静态、非静态成员的绑定)可以参考:
Boost库bind接口轻松实现类成员函数作为回调函数
更进一步关于bind的实现原理可以参考(不建议深入的了解其原理,没必要从轮子开始造车):
std和boost的function与bind实现剖析

 

posted on 2017-11-03 16:33  学习时间轴  阅读(1490)  评论(1编辑  收藏  举报