天天看點

c++11特性與cocos2d-x 3.0之std::bind與std::function

作者:​​陽光下的蒲公英​​

c++11特性與cocos2d-x 3.0之std::bind與std::function

昨天同僚讓幫忙寫一小功能,才發現cocos2d-x 3.0 和 cocos2d-x 3.0rc0 差别還是相當大的。

發現Label這一個控件,3.0就比rc0版本多了一個建立函數,更為關鍵的是3.0内的Label錨點是在ccp(0.5,0.5),而一直3.0rc0是ccp(0,0)。

累覺不愛。盡管cocos2d-x改變太快,相容性一次次的暴露出不足,但是,總歸是向好的方向進行。于是下載下傳了3.0來玩玩~

cocos new 出新的項目之後,仔細閱讀代碼,才發現了一句3.0差別于2.0的代碼:

1     auto closeItem = MenuItemImage::create(2                                            "CloseNormal.png",3                                            "CloseSelected.png",4                                            CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));      

2.0内的代碼用的不是CC_CALLBACK_1而是menu_selector.

CC_CALLBACK系列是3.0基于c++11的特性新增的。CC_CALLBACK系列的定義如下:

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

可以看出,CC_CALL_BACK系統後的數字,表示函數指針的參數個數。明白了這一點,選擇CC_CALLBACK時,就不會出錯鳥。

而看示例代碼時,還會發現一個有意思的使用方法:

1                 listener->onTouchesBegan = CC_CALLBACK_2(Layer::onTouchesBegan, this);      

此時不禁要問onTouchesBegan又是啥,為啥不能直接函數指針指派呢?

看定義就能明白了

1     std::function<void(const std::vector<Touch*>&, Event*)> onTouchesBegan;      

因為CC_CALLBACK系列是std::bind,而onTouchesBegan是std::function來定義的。那麼std::bind和std::function又有什麼差別呢?

有博文說:

function模闆類和bind模闆函數,使用它們可以實作類似函數指針的功能,但卻比函數指針更加靈活,特别是函數指向類的非靜态成員函數時。

std::function可以綁定到全局函數/類靜态成員函數(類靜态成員函數與全局函數沒有差別),如果要綁定到類的非靜态成員函數,則需要使用std::bind。

标準庫函數bind()和function()定義于頭檔案<functional>中(該頭檔案還包括許多其他函數對象),用于處理函數及函數參數。

std::bind綁定器

  • 将函數、成員函數和閉包轉成function函數對象
  • 将多元(n>1)函數轉成一進制函數或者(n-1)元函數。

bind()接受一個函數(或者函數對象,或者任何你可以通過"(...)"符号調用的事物),生成一個其有某一個或多個函數參數被“綁定”或重新組織的函數對象。(譯注:顧名思義,bind()函數的意義就像它的函數名一樣,是用來綁定函數調用的某些參數的。)例如:

1         int f(int, char, double);2         auto ff = bind(f, _1, 'c', 1.2);    // 綁定f()函數調用的第二個和第三個參數,傳回一個新的函數對象為ff,它隻帶有一個int類型的參數3         int x = ff(7);                //  f(7, 'c', 1.2);      

參數的綁定通常稱為"Currying"(譯注:Currying---“烹制咖喱燒菜”,此處意指對函數或函數對象進行加工修飾操作), "_1"是一個占位符對象,用于表示當函數f通過函數ff進行調用時,函數ff的第一個參數在函數f的參數清單中的位置。第一個參數稱為"_1", 第二個參數為"_2",依此類推。例如:

1         int f(int, char, double);2         auto frev = bind(f, _3, _2, _1);        // 翻轉參數順序3         int x = frev(1.2, 'c', 7);            // f(7, 'c', 1.2);      

    此處,auto關鍵位元組約了我們去推斷bind傳回的結果類型的工作。

    我們無法使用bind()綁定一個重載函數的參數,我們必須顯式地指出需要綁定的重載函數的版本:

int g(int);2         double g(double);3 4         auto g1 = bind(g, _1);                // 錯誤:調用哪一個g() ?5         auto g2 = bind( (double(*)(double))g, _1);    // 正确,但是相當醜陋      
1 void H(int a); 2 //綁定全局函數 3 auto f11 = std::bind(H, std::placeholders::_1); 4 auto的類型實際上是std::function<void(int)> 5  6 //綁定帶參數的成員函數 7 std::function<void (char*, int)> f = std::bind(&ReadHandler::ConnectPreProcess, this, std::placeholders::_1, std::placeholders::_1); 8  9 //三元函數轉換成一進制函數10 int f(int, char, double);11 // 綁定f()函數調用的第二個和第三個參數,12 // 傳回一個新的函數對象為ff,它隻帶有一個int類型的參數13 auto ff = bind(f, _1, ‘c’, 1.2);    14 int x = ff(7);      

自己寫代碼示例如下:

int Func(int x, int y);
auto bf1 = std::bind(Func, 10, std::placeholders::_1);
bf1(20); ///< same as Func(10, 20)int HelloWorld::AddFunc( int a, int b )
{
    return a + b;
}
bool HelloWorld::init()
{

    auto bf2 = std::bind(&HelloWorld::AddFunc,this , std::placeholders::_1, std::placeholders::_2 );
    auto result1 = bf2(10, 20); ///< same as a.Func(10, 20)
    std::function< int(int)> bf3 = std::bind(&HelloWorld::AddFunc, this, std::placeholders::_1, 100);
    auto result2 = bf3(10); ///< same as a.Func(10, 100)
}      

上面的例子中,bf1是把一個兩個參數普通函數的第一個參數綁定為10,生成了一個新的一個參數的可調用實體體; bf2是把一個類成員函數綁定了類對象,生成了一個像普通函數一樣的新的可調用實體; bf3是把類成員函數綁定了類對象和第二個參數,生成了一個新的std::function對象。看懂了上面的例子,下面我們來說說使用bind需要注意的一些事項:

  • (1)bind預先綁定的參數需要傳具體的變量或值進去,對于預先綁定的參數,是pass-by-value的
  • (2)對于不事先綁定的參數,需要傳std::placeholders進去,從_1開始,依次遞增。placeholder是pass-by-reference的
  • (3)bind的傳回值是可調用實體,可以直接賦給std::function對象
  • (4)對于綁定的指針、引用類型的參數,使用者需要保證在可調用實體調用之前,這些參數是可用的
  • (5)類的this可以通過對象或者指針來綁定

std::function

它是函數、函數對象、函數指針、和成員函數的包裝器,可以容納任何類型的函數對象,函數指針,引用函數,成員函數的指針。

以統一的方式處理函數、函數對象、函數指針、和成員函數。允許儲存和延遲執行函數。

  • 函數和成員函數作為function

function是一個擁有任何可以以"(...)"符号進行調用的值的類型。特别地,bind的傳回結果可以指派給function類型。function十分易于使用。(譯注:更直覺地,可以把function看成是一種表示函數的資料類型,就像函數對象一樣。隻不過普通的資料類型表示的是資料,function表示的是函數這個抽象概念。)例如:

1 typedef std::function<float (int x, int y)> f ;// 構造一個函數對象,它能表示的是一個傳回值為float,兩個參數為int,int的函數   2 struct int_div {        // 構造一個可以使用"()"進行調用的函數對象類型   3     float operator() (int x, int y) const { return ((float)x)/y; }; 4 }; 5  6 void HelloWorld::testing() 7 { 8     f f1= int_div();                    // 指派   9     auto result3 = f1( 10, 2);10 }      

成員函數可被看做是帶有額外參數的自由函數:

1 struct int_div {        // 構造一個可以使用"()"進行調用的函數對象類型   2     float operator() (int x, int y) const { return ((float)x)/y; }; 3     int int_div_fun( int x ){ return x; }; 4 }; 5 typedef std::function<int (int_div*, int)> f_2; 6  7 bool HelloWorld::init() 8 { 9     f_2 f2 = std::mem_fn(&int_div::int_div_fun);            // 指向成員函數10 11     int_div int_div_object;12     int v = f2(&int_div_object, 5);  // 在對象x上用參數5調用X::foo()13     std::function<int (int)> ff = std::bind( f2, &int_div_object, std::placeholders::_1);    // f的第一個參數是&x14     v = ff(5);                // 調用x.foo(5)15 16 17 }      

ps:被vs2012的bug給坑了。因為看網上的代碼于是剛開始第9行是這麼寫的:f_2 f2 = &int_div::int_div_fun; 

然後就報錯誤:​

​Error 1 error C2664:'std::_Func_class<_Ret,_V0_t,_V1_t>::_Set' : cannot convert parameter 1from '_Myimpl *' to 'std::_Func_base<_Rx,_V0_t,_V1_t> *'​

查了一下,vs2010沒有這個編譯錯誤,但是2012有。2012必須得加上std::mem_fn才能編譯。

  • 可以用function取代函數指針。因為它可以儲存函數延遲執行,是以比較适合作為回調函數,也可以把它看做類似于c#中特殊的委托,隻有一個成員的委托。
1 struct int_div {        // 構造一個可以使用"()"進行調用的函數對象類型   2     float operator() (int x, int y) const { return ((float)x)/y; }; 3     int int_div_fun( int x ){ return x; }; 4  5     int_div( std::function<void()>& f ):m_callback(f){}; 6     void Notify() 7     { 8         m_callback(); 9     }10     std::function<void()> m_callback;11 };      
  • function還可以作為函數入參,這樣可以在函數外部控制函數的内部行為了,讓我們的函數變得更加靈活。
void Foo(int x, std::function<void(int)>& f)
{if(x%2==0)
f(x);
}
void G(int x)
{
cout<<x<<endl;
}
void H(int x)
{
cout<<x+2<<endl;
}
void TestFoo()
{
auto f = std::bind(G, std::placeholders::_1); 
Foo(4, f);
//在Foo函數外面更改f的行為
f = std::bind(H, std::placeholders::_1);
Foo(4, f);
}      

c++11中推出function是為了泛化函數對象,函數指針,引用函數,成員函數的指針,讓我們可以按更統一的方式寫出更加泛化的代碼;推出bind是為了替換和增強之前标準庫的bind1st和bind2st,讓我們的用起來更友善!

更多精品iOS、Cocos、移動設計課程請關注智捷課堂官方網站:​​http://www.zhijieketang.com​​