1. 程式人生 > 實用技巧 >C++11中的std::bind

C++11中的std::bind

看看這段程式碼

這幾天學習Cocos2d-x,看到了以下的一段程式碼:

1 // new callbacks based on C++11
2 #define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__)
3  
4 #define CC_CALLBACK_1(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__)
5
6 #define CC_CALLBACK_2(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__) 7 8 #define CC_CALLBACK_3(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)

都是定義的一些巨集,如果你看了上面的這段程式碼,覺的很簡單,那麼這篇文章你完全可以pass了;如果你對上面定義的這些巨集,完全不知道是什麼意思,那麼,這篇文章就屬於你,完全屬於你的菜。

通過這篇文章,我將帶你進入C++11中std::bind的世界,讓我們起航吧。

先來看看std::bind1st和std::bind2nd

bind是這樣一種機制,它可以預先把指定可呼叫實體的某些引數繫結到已有的變數,產生一個新的可調 用實體,這種機制在回撥函式的使用過程中也頗為有用。C++98中,有兩個函式bind1st和bind2nd,它們分別可以用來繫結functor的第 一個和第二個引數,它們都是隻可以繫結一個引數。各種限制,使得bind1st和bind2nd的可用性大大降低。

如果通過上面的內容,你還沒有明白std::bind1st和std::bind2nd到底是何方聖神到底是什麼東西,那就看這段程式碼示例:

 1 #include <iostream>
 2 #include <functional>
 3 #include <algorithm>
 4 #include <vector>
 5 using namespace std;
 6  
 7 int main()
 8 {
 9     vector<int> coll;
10     for (int i = 1; i <= 10; ++i)
11     {
12         coll.push_back(i);
13     }
14  
15     // 查詢元素值大於10的元素的個數
16     // 也就是使得10 < elem成立的元素個數 
17     int res = count_if(coll.begin(), coll.end(), bind1st(less<int>(), 10));
18     cout << res << endl;
19  
20     // 查詢元素值小於10的元素的個數
21     // 也就是使得elem < 10成立的元素個數 
22     res = count_if(coll.begin(), coll.end(), bind2nd(less<int>(), 10));
23     cout << res << endl;
24  
25     return 0;
26 }

通過上面的程式碼明白了std::bind1st和std::bind2nd了麼?還沒有明白?好吧,我接著往細了講。

對於上面的程式碼,less<int>()其實是一個仿函式,如果沒有std::bind1st和std::bind2nd,那麼我們可以這樣使用less<int>(),程式碼如下:

1 less<int> functor = less<int>();
2 bool bRet = functor(10, 20); // 返回true

看到了麼?less<int>()這個仿函式物件是需要兩個引數的,比如10<20進行比較,那麼10叫做left引數,20叫做right引數。

  • 當使用std::bind1st的時候,就表示綁定了left引數,也就是left引數不變了,而right引數就是對應容器中的element;
  • 當使用std::bind2nd的時候,就表示綁定了right引數,也就是right引數不變了,而left引數就是對應容器中的element。

這下應該講明白了。

再來看看std::bind

C++11中提供了std::bind。bind()函式的意義就像它的函式名一樣,是用來繫結函式呼叫的某些引數的。

bind的思想實際上是一種延遲計算的思想,將可呼叫物件儲存起來,然後在需要的時候再呼叫。而且這種繫結是非常靈活的,不論是普通函式、函式物件、還是成員函式都可以繫結,而且其引數可以支援佔位符,比如你可以這樣繫結一個二元函式auto f = bind(&func, _1, _2);,呼叫的時候通過f(1,2)實現呼叫。

簡單的認為就是std::bind就是std::bind1ststd::bind2nd的加強版。

怎麼使用std::bind

一個知識點厲不厲害,歸根到底還是要經過實踐的考驗,下面就來看看std::bind到底怎麼用。

先看看《C++11中的std::function》中那段程式碼,std::function可以繫結全域性函式,靜態函式,但是繫結類的成員函式時,必須要藉助std::bind的幫忙。但是話又說回來,不借助std::bind也是可以完成的,只需要傳一個*this變數進去就好了,比如:

 1 #include <iostream>
 2 #include <functional>
 3 using namespace std;
 4  
 5 class View
 6 {
 7 public:
 8     void onClick(int x, int y)
 9     {
10         cout << "X : " << x << ", Y : " << y << endl;
11     }
12 };
13  
14 // 定義function型別, 三個引數
15 function<void(View, int, int)> clickCallback;
16  
17 int main(int argc, const char * argv[])
18 {
19     View button;
20  
21     // 指向成員函式
22     clickCallback = &View::onClick;
23  
24     // 進行呼叫
25     clickCallback(button, 10, 123);
26     return 0;
27 }

再來一段示例談談怎麼使用std::bind程式碼:

 1 #include <iostream>
 2 #include <functional>
 3 using namespace std;
 4  
 5 int TestFunc(int a, char c, float f)
 6 {
 7     cout << a << endl;
 8     cout << c << endl;
 9     cout << f << endl;
10  
11     return a;
12 }
13  
14 int main()
15 {
16     auto bindFunc1 = bind(TestFunc, std::placeholders::_1, 'A', 100.1);
17     bindFunc1(10);
18  
19     cout << "=================================\n";
20  
21     auto bindFunc2 = bind(TestFunc, std::placeholders::_2, std::placeholders::_1, 100.1);
22     bindFunc2('B', 10);
23  
24     cout << "=================================\n";
25  
26     auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
27     bindFunc3(100.1, 30, 'C');
28  
29     return 0;
30 }

上面這段程式碼主要說的是bind中std::placeholders的使用。 std::placeholders是一個佔位符。當使用bind生成一個新的可呼叫物件時,std::placeholders表示新的可呼叫物件的第 幾個引數和原函式的第幾個引數進行匹配,這麼說有點繞。比如:

1 auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
2  
3 bindFunc3(100.1, 30, 'C');

可以看到,在bind的時候,第一個位置是TestFunc,除了這個,引數的第一個位置為佔位符std::placeholders::_2,這就表示,呼叫bindFunc3的時候,它的第二個引數和TestFunc的第一個引數匹配,以此類推。

以下是使用std::bind的一些需要注意的地方:

  • bind預先繫結的引數需要傳具體的變數或值進去,對於預先繫結的引數,是pass-by-value的;
  • 對於不事先繫結的引數,需要傳std::placeholders進去,從_1開始,依次遞增。placeholder是pass-by-reference的;
  • bind的返回值是可呼叫實體,可以直接賦給std::function物件;
  • 對於繫結的指標、引用型別的引數,使用者需要保證在可呼叫實體呼叫之前,這些引數是可用的;
  • 類的this可以通過物件或者指標來繫結。

為什麼要用std::bind

當我們厭倦了使用std::bind1ststd::bind2nd的時候,現在有了std::bind,你完全可以放棄使用std::bind1ststd::bind2nd了。std::bind繫結的引數的個數不受限制,繫結的具體哪些引數也不受限制,由使用者指定,這個bind才是真正意義上的繫結。

在Cocos2d-x中,我們可以看到,使用std::bind生成一個可呼叫物件,這個物件可以直接賦值給std::function物件;在類中有一個std::function的變數,這個std::functionstd::bind來賦值,而std::bind繫結的可呼叫物件可以是Lambda表示式或者類成員函式等可呼叫物件,這個是Cocos2d-x中的一般用法。

以後遇到了“奇葩”用法再繼續總結了,一次也總結不完的。

總結

又是一篇總結怎麼使用的文章,如果你覺的看的不過癮,覺的我的文章寫的不痛不癢的,還想看點更深的東西,比如std::bind是如何實現的啊?好吧,這篇文章確實沒有說這些深層次的東西,推薦這篇文章《bind原理圖釋》,希望這篇文章能滿足你哦。