Boost::bind使用詳解

1.Boost::bind

  在STL中,咱們常常須要使用bind1st,bind2st函數綁定器和fun_ptr,mem_fun等函數適配器,這些函數綁定器和函數適配器使用起來比較麻煩,須要根據是全局函數仍是類的成員函數,是一個參數仍是多個參數等作出不一樣的選擇,並且有些狀況使用STL提供的不能知足要求,因此若是能夠咱們最好使用boost提供的bind,它提供了統一的接口,提供了更多的支持,好比說它增長了shared_ptr,虛函數,類成員的綁定。html

2.bind的工做原理

  bind並非一個單獨的類或函數,而是很是龐大的家族,依據綁定的參數的個數和要綁定的調用對象的類型,總共有數十種不一樣的形式,編譯器會根據具體的綁定代碼制動肯定要使用的正確的形式,bind的基本形式以下:ios

1 template<class R,class F> bind(F f);
2 template<class R,class F,class A1> bind(F f,A1 a1);
3 namespace
4 {
5 boost::arg<1> _1;
6 boost::arg<2> _2;
7 boost::arg<3> _3;
8 …..                                     //其餘6個佔位符
9 };
View Code

  bind接收的第一個參數必須是一個可調用的對象f,包括函數函數指針函數對象、和成員函數指針,以後bind最多接受9個參數參數數量必須與f的參數數量相等,這些參數被傳遞給f做爲入參。 綁定完成後,bind返回一個函數對象,它內部保存了f的拷貝,具備operator()返回值類型自動推導f的返回類型。在發生調用時這個函數對象將把以前存儲的參數轉發給f完成調用。例如,有一個函數func,它的形式是:程序員

1 func(a1,a2);

那麼,他將等價於一個具備無參operator()的bind函數對象調用:算法

1 bind(func,a1,a2)();

  這是bind最簡單的形式,bind表達式存儲了func和a一、a2的拷貝,產生了一個臨時函數對象。由於func接收兩個參數,而a1和a2的拷貝傳遞給func完成真正的函數調用。less

  bind的真正威力在於它的佔位符,它們分別定義爲_1,_2,_3,一直到 _9,位於一個匿名的名字空間。佔位符能夠取代bind參數的位置,在發生調用時接受真正的參數佔位符的名字表示它在調用式中的,而在綁定的表達式中沒有沒有順序的要求,_1不必定必須第一個出現,也不必定只出現一次,例如:ide

1 bind(func,_2,_1)(a1,a2);

  返回一個具備兩個參數的函數對象,第一個參數將放在func的第二個位置,而第二個參數則放在第一個位置,調用時等價於:函數

1 func(a2,a1);

3.經常使用的函數對象工具

(1)bind1st,bind2st函數綁定器,把二元函數對象變爲一元函數對象
(2)mem_fun,把成員函數變爲函數對象
(3)fun_ptr,把通常的全局函數變爲函數對象
(4)boost::bind(),包含了以上全部的功能工具

4.bind與其餘函數對象工具區別

4.1 區別與mem_fun和fun_ptr

 1 #include <functional>
 2 #include <iostream>
 3 #include <string>
 4 #include "boost/bind.hpp"
 5 class some_class 
 6 {
 7 public:      
 8     void print_string(const std::string& s) const
 9     {    
10         std::cout << s << '\n'; 
11     }
12     void print_classname()
13     {
14         std::cout << "some_class" << std::endl;
15     }
16 };
17 void print_string(const std::string s) 
18 {  std::cout << s << '\n';
19 }
20 void print_functionname()
21 {
22     std::cout << "Print_functionname" <<std::endl;
23 }
24 int main() 
25 {  
26     std::ptr_fun(&print_string)("hello1");
27     //std::ptr_fun<void>(&print_functionname);
28     some_class sc0;
29     std::mem_fun_ref(&some_class::print_classname)(sc0);
30     std::mem_fun_ref<void,some_class>(&some_class::print_classname)(sc0);
31     //std::mem_fun1_ref<void,some_class,const std::stirng>(&some_class::print_string)(sc0,"hello2");
32 
33     (boost::bind(&print_string,_1))("Hello func!");  
34     boost::bind(&print_functionname);
35     some_class sc;  
36     (boost::bind(&some_class::print_classname,_1)(sc));
37     (boost::bind(&some_class::print_string,_1,_2))(sc,"Hello member!");
38 }
View Code

4.2 區別與bind1st和bind2st

 1 #include <functional>
 2 #include <iostream>
 3 #include <string>
 4 #include <vector>
 5 #include <algorithm>
 6 #include "boost/bind.hpp"
 7 void main()
 8 {
 9     std::vector<int> ints;
10     ints.push_back(7);
11     ints.push_back(4);
12     ints.push_back(12);
13     ints.push_back(10);
14     int count=std::count_if(ints.begin(),  
15         ints.end(), 
16         boost::bind(std::logical_and<bool>(),boost::bind(std::greater<int>(),_1,5),boost::bind(std::less_equal<int>(),_1,10))
17         );
18     std::cout << count << '\n';
19     std::vector<int>::iterator int_it=std::find_if(ints.begin(),  
20         ints.end(),  
21         boost::bind(std::logical_and<bool>(),boost::bind(std::greater<int>(),_1,5),boost::bind(std::less_equal<int>(),_1,10))
22         );
23     if (int_it!=ints.end()) 
24     {  std::cout << *int_it << '\n';}
25 
26 }
View Code

4.3 區別傳ref和傳instance

 1 // bind instance or reference
 2 #include <functional>
 3 #include <iostream>
 4 #include <string>
 5 #include <vector>
 6 #include <algorithm>
 7 #include "boost/bind.hpp"
 8 class tracer 
 9 {
10 public: 
11     tracer() {    std::cout << "tracer::tracer()\n";  } 
12     tracer(const tracer& other) {    std::cout << "tracer::tracer(const tracer& other)\n";  } 
13     tracer& operator=(const tracer& other)
14     {    std::cout <<      "tracer& tracer::operator=(const tracer& other)\n";    return *this;  } 
15     ~tracer() {    std::cout << "tracer::~tracer()\n"; 
16     } 
17     void print(const std::string& s) const
18     {    std::cout << s << '\n';  }
19 };
20 
21 void main()
22 {
23     tracer t;
24     boost::bind(&tracer::print,t,_1)(std::string("I'm called on a copy of t\n"));
25     tracer t1;
26     boost::bind(&tracer::print,boost::ref(t1),_1)(  std::string("I'm called directly on t\n"));
27 
28 }
View Code

5.bind的應用場景

5.1 綁定普通函數

  bind能夠綁定普通函數,包括函數函數指針,假設我麼有以下的函數定義:this

1 int f(int a,int b){return a+b;}   //二元函數
2 int g(int a,int b,int c) {return a+b+c;} //三元函數
3 typedef int (*f_type)(int,int);      //函數指針定義
4 typedef int (*g_type)(int,int,int); //函數指針定義
View Code

  那麼,bind(f,1,2) 將返回一個無參調用函數對象,等價於f(1,2),bind(q,1,2,3)一樣返回一個無參調用的函數對象,等價於 g(1,2,3)。這兩個綁定表達式沒有使用佔位符,而是給出了所有的具體參數,代碼:spa

1 cout<<bind(f,1,2)()<<endl;
2 cout<<bind(g,1,2,3)()<<endl;

  至關於:

1 cout<<f(1,2)<<endl;
2 cout<<g(1,2,3)<<endl; 

  使用佔位符bind能夠有更多的變化,這纔是它真正應該作的工做,下面列出了一些佔位符的用法:

1 bind(f,_1,9)(x);  //f(x,9),至關於bind2nd(f,9)
2 bind(f,_1,_2)(x,y); //f(x,y)
3 bind(f,_2,_1)(x,y); //f(y,x)
4 bind(f,_1,_1)(x,y); //f(x,x),y參數被忽略
5 bind(g,_1,8,_2)(x,y) //g(x,8,y)
6 bind(g,_3,_2_2)(x,y,z) //g(z,y,y),x參數被忽略
View Code

  注意:必須綁定表達式中提供函數要求全部參數,不管是真實參數仍是佔位符都可以佔位符能夠出現也能夠不出現,出現的順序數量沒有限定,但不能使用超過函數參數數量佔位符,好比在綁定f是不能用_3,在綁定g時不能使用_4,也不能寫bind(f,_1,_2,_2),這樣的形式會致使編譯錯誤。bind徹底能夠代替標準庫中的bind1st和bind2nd,使用bind(f,N,_1)和bind(f,_1,N)。要注意的是它們均使用了一個佔位符,bind1st把第一個參數用固定值代替,bind2nd把第二個參數用固定值代替。bind也能夠綁定函數指針,用法相同,例如:

1 f_type pf = f;
2 g_type pg = g;
3 int x =1,y=2,z=3;
4 cout<<bind(pf,_1,9)(x)<<endl; //(*pf(x,9))
5 cout<<bind(pg,_3,_2,_2)(x,y,z)<<endl; //(*pg)(z,y,y)
View Code

5.2 bind綁定成員函數

  類的成員函數不一樣於普通的函數,由於成員函數指針不能直接調用operator(),它必須綁定到一個對象指針,而後才能獲得this指針進而調用成員函數。所以bind須要 「犧牲」一個佔位符,要求提供一個類的實例引用或者指針,經過對象做爲第一個參數來調用成員函數,即:

1 bind(&X::func,x,_1,_2,…)

  這意味着使用成員函數時只能最多綁定8個參數。例如,有一個類demo

1 struct demo
2 {
3     int f(int a,int b){return a+b;}
4 };

  那麼,下面的bind表達式都是成立的:

1 demo a,&ra = a;    //類的實例對象和引用
2 demo * p = & a;     //指針
3 cout<<bind(&demo::f,a,_1,20)(10)<<endl;
4 cout<<bind(&demo::f,ra,_2,_1)(10,20)<<endl;
5 cout<<bind(&demo::f,p,_1,_2)(10,20)<<endl;
View Code

  注意:咱們必須成員函數前面加上取地址的操做符&,代表這是一個成員函數指針,不然會沒法編譯經過,這是與綁定函數的一個小小的不一樣。bind一樣支持綁定虛擬成員函數,用法與非虛函數相同,虛函數的行爲將由實際調用發生時的實例來決定。

5.3 bind綁定成員變量

  bind的另外一個對類的操做是它能夠綁定public成員變量,用法與綁定成員函數相似,只須要把成員變量名像一個成員函數同樣去使用。例如:

1 vector<point> v(10);
2 vector<int> v2(10);
3 transform(v.begin(),v.end(),v2.begin(),bind(&point::x,_1));
4 BOOST_FOREACH(int x,v2) cout<<x<<「,」;

  代碼中的bind(&point::x,_1)取出point對象的成員變量x,transform算法調用bind表達式操做容器v,逐個把成員變量填入到v2中。看到這裏感到有點困惑,有點難以理解:bind返回的是一個函數對象,該對象對「()」進行了重載,在transform調用該重載函數應該是將v中的每個成員變量做爲參數傳進去,從而取出每個元素的x變量。

  使用bind能夠實現SGISTL/STLport中的非標準函數適配器select1st和select2nd的功能,直接選擇出pair對象first和second成員,例如:

1 typedef pair<int,string> pair_t;
2 pair_t p(123,」string」);
3 cout<<bind(&pair_t::first,p)()<<endl;
4 cout<<bind(&pair_t::second,p)()<<endl;

5.4 綁定函數對象

  bind不只可以綁定函數和函數指針,也可以綁定任意的函數對象,包括標準庫中預約義的函數對象。若是函數對象有內部類型定義result_type,那麼bind能夠自動推導返回值類型,用法與普通函數同樣。但若是函數對象沒有定義result_type,則須要在綁定形式上作一點改動,用模板參數指明返回類型,像這樣:

1 bind<result_type>(Functor,…);

  標準庫和Boost庫中的大部分函數都具備result_type定義,所以不須要特別的形式就能夠直接使用bind,例如:

1 bind(std::greater<int>(),_1,10);  //檢查 x>10
2 bind(plus<int>(),_1,_2); //執行 x+y
3 bind(modulus<int>(),_1,3), //執行 x%3

  對於自定義的函數對象,若是沒有result_type類型定義,例如:

1 struct f
2 {
3     int operator() (int a,int b) {return a +b;}
4 };

  那麼咱們必須指明

1 bind<int> (f(),_1,_2)(10,20)<<endl;

  這種寫法所燒會有些不方便,所以,在編寫本身的函數對象時,最好遵循規範爲它們增長內部typedef result_type,這將使函數對象與其餘的標準庫和Boost庫組件配合工做。

5.5 使用ref庫

  bind採用拷貝的方式保存綁定對象參數,這意味着綁定表達式中的每個變量都會有一份拷貝,若是函數對象或值參數很大、拷貝代價很高,或者沒法拷貝,那麼bind的使用就會受到限制。所以bind庫能夠搭配ref庫使用,ref庫包裝了對象的引用,可讓bind存儲對象引用的拷貝,從而下降了拷貝的代價。但這也帶來了一個隱患,由於有時候bind的調用可能會延後好久,程序員必須保證bind被調用時引用是有效的。若是調用是引用的變量或者函數對象你被銷燬了,那麼將會發生未定義行爲。ref配合bind用法的代碼以下:

1 int x = 10;
2 cout<<bind(g,_1,cref(x),ref(x))(10)<<endl;
3 f af;
4 cout<<bind<int>(ref(af),_1,_2)(10,20)<<endl;

  下面的代碼則由於引用失效,引起未定義行爲:

 1 int x = 10;
 2 BOOST_AUTO(r,ref(x));
 3 {
 4     int * y = new int(20);
 5     r = ref(*y);
 6     cout<<r<<endl;
 7     cout<<bind(g,r,1,1)()<<endl;
 8     delete y;
 9 }
10 cout<<bind(g,r,1,1)()<<endl;
View Code

5.6 存儲bind表達式

  不少時候咱們須要把寫好的bind表達式存儲起來,以便稍後再度使用,但bind表達式生成的對象類型聲明很是複雜,一般沒法寫出正確的類型,所以可使用typeof庫的BOOST_AUTO宏來輔助咱們,例如:

1 BOOST_AUTO(x,bind(greater<int>(),_1,_2));
2 cout<<x(10,20)<<endl;

  bind能夠嵌套,一個bind表達式生成的函數對象能夠被另外一個bind再綁定,從而實現相似f(g(x))的形式,若是咱們有f(x)和g(x)兩個函數,那麼f(g(x))的bind表達式就是:

1 bind(f,bind(g,_1))(x)

  使用bind的嵌套用法必須當心,它不太容易一次寫對,也不太容易理解,超過兩個以上的bind表達式一般只能被編譯器讀懂,必須配合良好的註釋纔可以使用bind嵌套用法。

5.7 綁定非標準函數

  bind能夠適配任何一種C++中的函數,但標準形式bind(f,…)不是適用所用的狀況,有些非標準函數沒法制動推導出返回值類型,典型的就是C中的可變參數函數printf()。必須用bind<int>(printf,…)(…),例如:

1 bind<int>(printf,」%d+%d=%d\n」,_1,_1,_2)(6,7);

  bind的標準形式不能支持使用了不一樣的調用方式,如:__stdcall__fastcallextern」C」的函數,一般bind把他們看作函數對象,須要顯示的指定bind的返回值類型才能綁定。

 

原文連接:

http://www.cppblog.com/mzty/archive/2007/09/05/31622.html

http://www.cnblogs.com/yu-chao/p/3979124.html

相關文章
相關標籤/搜索