C++ 11 std::function和std::bind使用詳解
cocos new 出新的項(xiàng)目之后,仔細(xì)閱讀代碼,才發(fā)現(xiàn)了一句3.0區(qū)別于2.0的代碼:
auto closeItem = MenuItemImage::create( "CloseNormal.png", "CloseSelected.png", CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
2.0內(nèi)的代碼用的不是CC_CALLBACK_1而是menu_selector.
CC_CALLBACK系列是3.0基于c++11的特性新增的。CC_CALLBACK系列的定義如下:
// new callbacks based on C++11 #define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__) #define CC_CALLBACK_1(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__) #define CC_CALLBACK_2(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__) #define CC_CALLBACK_3(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)
可以看出,CC_CALL_BACK系統(tǒng)后的數(shù)字,表示函數(shù)指針的參數(shù)個(gè)數(shù)。明白了這一點(diǎn),選擇CC_CALLBACK時(shí),就不會(huì)出錯(cuò)鳥(niǎo)。
而看示例代碼時(shí),還會(huì)發(fā)現(xiàn)一個(gè)有意思的使用方法:
listener->onTouchesBegan = CC_CALLBACK_2(Layer::onTouchesBegan, this);
此時(shí)不禁要問(wèn)onTouchesBegan又是啥,為啥不能直接函數(shù)指針賦值呢?
看定義就能明白了
std::function<void(const std::vector<Touch*>&, Event*)> onTouchesBegan;
因?yàn)镃C_CALLBACK系列是std::bind,而onTouchesBegan是std::function來(lái)定義的。那么std::bind和std::function又有什么區(qū)別呢?
有博文說(shuō):
function模板類(lèi)和bind模板函數(shù),使用它們可以實(shí)現(xiàn)類(lèi)似函數(shù)指針的功能,但卻比函數(shù)指針更加靈活,特別是函數(shù)指向類(lèi)的非靜態(tài)成員函數(shù)時(shí)。
std::function可以綁定到全局函數(shù)/類(lèi)靜態(tài)成員函數(shù)(類(lèi)靜態(tài)成員函數(shù)與全局函數(shù)沒(méi)有區(qū)別),如果要綁定到類(lèi)的非靜態(tài)成員函數(shù),則需要使用std::bind。
標(biāo)準(zhǔn)庫(kù)函數(shù)bind()和function()定義于頭文件<functional>中(該頭文件還包括許多其他函數(shù)對(duì)象),用于處理函數(shù)及函數(shù)參數(shù)。
std::bind綁定器
- 將函數(shù)、成員函數(shù)和閉包轉(zhuǎn)成function函數(shù)對(duì)象
- 將多元(n>1)函數(shù)轉(zhuǎn)成一元函數(shù)或者(n-1)元函數(shù)。
bind()接受一個(gè)函數(shù)(或者函數(shù)對(duì)象,或者任何你可以通過(guò)"(...)"符號(hào)調(diào)用的事物),生成一個(gè)其有某一個(gè)或多個(gè)函數(shù)參數(shù)被“綁定”或重新組織的函數(shù)對(duì)象。(譯注:顧名思義,bind()函數(shù)的意義就像它的函數(shù)名一樣,是用來(lái)綁定函數(shù)調(diào)用的某些參數(shù)的。)例如:
int f(int, char, double); auto ff = bind(f, _1, 'c', 1.2); // 綁定f()函數(shù)調(diào)用的第二個(gè)和第三個(gè)參數(shù),返回一個(gè)新的函數(shù)對(duì)象為ff,它只帶有一個(gè)int類(lèi)型的參數(shù) int x = ff(7); // f(7, 'c', 1.2);
參數(shù)的綁定通常稱(chēng)為"Currying"(譯注:Currying---“烹制咖喱燒菜”,此處意指對(duì)函數(shù)或函數(shù)對(duì)象進(jìn)行加工修飾操作), "_1"是一個(gè)占位符對(duì)象,用于表示當(dāng)函數(shù)f通過(guò)函數(shù)ff進(jìn)行調(diào)用時(shí),函數(shù)ff的第一個(gè)參數(shù)在函數(shù)f的參數(shù)列表中的位置。第一個(gè)參數(shù)稱(chēng)為"_1", 第二個(gè)參數(shù)為"_2",依此類(lèi)推。例如:
int f(int, char, double); auto frev = bind(f, _3, _2, _1); // 翻轉(zhuǎn)參數(shù)順序 int x = frev(1.2, 'c', 7); // f(7, 'c', 1.2);
此處,auto關(guān)鍵字節(jié)約了我們?nèi)ネ茢郻ind返回的結(jié)果類(lèi)型的工作。
我們無(wú)法使用bind()綁定一個(gè)重載函數(shù)的參數(shù),我們必須顯式地指出需要綁定的重載函數(shù)的版本:
int g(int); double g(double); auto g1 = bind(g, _1); // 錯(cuò)誤:調(diào)用哪一個(gè)g() ? auto g2 = bind( (double(*)(double))g, _1); // 正確,但是相當(dāng)丑陋
void H(int a); //綁定全局函數(shù) auto f11 = std::bind(H, std::placeholders::_1); auto的類(lèi)型實(shí)際上是std::function<void(int)> //綁定帶參數(shù)的成員函數(shù) std::function<void (char*, int)> f = std::bind(&ReadHandler::ConnectPreProcess, this, std::placeholders::_1, std::placeholders::_1); //三元函數(shù)轉(zhuǎn)換成一元函數(shù) int f(int, char, double); // 綁定f()函數(shù)調(diào)用的第二個(gè)和第三個(gè)參數(shù), // 返回一個(gè)新的函數(shù)對(duì)象為ff,它只帶有一個(gè)int類(lèi)型的參數(shù) auto ff = bind(f, _1, ‘c', 1.2); int x = ff(7);
自己寫(xiě)代碼示例如下:
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是把一個(gè)兩個(gè)參數(shù)普通函數(shù)的第一個(gè)參數(shù)綁定為10,生成了一個(gè)新的一個(gè)參數(shù)的可調(diào)用實(shí)體體; bf2是把一個(gè)類(lèi)成員函數(shù)綁定了類(lèi)對(duì)象,生成了一個(gè)像普通函數(shù)一樣的新的可調(diào)用實(shí)體; bf3是把類(lèi)成員函數(shù)綁定了類(lèi)對(duì)象和第二個(gè)參數(shù),生成了一個(gè)新的std::function對(duì)象??炊松厦娴睦?,下面我們來(lái)說(shuō)說(shuō)使用bind需要注意的一些事項(xiàng):
(1)bind預(yù)先綁定的參數(shù)需要傳具體的變量或值進(jìn)去,對(duì)于預(yù)先綁定的參數(shù),是pass-by-value的
(2)對(duì)于不事先綁定的參數(shù),需要傳std::placeholders進(jìn)去,從_1開(kāi)始,依次遞增。placeholder是pass-by-reference的
(3)bind的返回值是可調(diào)用實(shí)體,可以直接賦給std::function對(duì)象
(4)對(duì)于綁定的指針、引用類(lèi)型的參數(shù),使用者需要保證在可調(diào)用實(shí)體調(diào)用之前,這些參數(shù)是可用的
(5)類(lèi)的this可以通過(guò)對(duì)象或者指針來(lái)綁定
std::function
它是函數(shù)、函數(shù)對(duì)象、函數(shù)指針、和成員函數(shù)的包裝器,可以容納任何類(lèi)型的函數(shù)對(duì)象,函數(shù)指針,引用函數(shù),成員函數(shù)的指針。
以統(tǒng)一的方式處理函數(shù)、函數(shù)對(duì)象、函數(shù)指針、和成員函數(shù)。允許保存和延遲執(zhí)行函數(shù)。
函數(shù)和成員函數(shù)作為function
function是一個(gè)擁有任何可以以"(...)"符號(hào)進(jìn)行調(diào)用的值的類(lèi)型。特別地,bind的返回結(jié)果可以賦值給function類(lèi)型。function十分易于使用。(譯注:更直觀地,可以把function看成是一種表示函數(shù)的數(shù)據(jù)類(lèi)型,就像函數(shù)對(duì)象一樣。只不過(guò)普通的數(shù)據(jù)類(lèi)型表示的是數(shù)據(jù),function表示的是函數(shù)這個(gè)抽象概念。)例如:
typedef std::function<float (int x, int y)> f ;// 構(gòu)造一個(gè)函數(shù)對(duì)象,它能表示的是一個(gè)返回值為float,兩個(gè)參數(shù)為int,int的函數(shù) struct int_div { // 構(gòu)造一個(gè)可以使用"()"進(jìn)行調(diào)用的函數(shù)對(duì)象類(lèi)型 float operator() (int x, int y) const { return ((float)x)/y; }; }; void HelloWorld::testing() { f f1= int_div(); // 賦值 auto result3 = f1( 10, 2); }
成員函數(shù)可被看做是帶有額外參數(shù)的自由函數(shù):
struct int_div { // 構(gòu)造一個(gè)可以使用"()"進(jìn)行調(diào)用的函數(shù)對(duì)象類(lèi)型 float operator() (int x, int y) const { return ((float)x)/y; }; int int_div_fun( int x ){ return x; }; }; typedef std::function<int (int_div*, int)> f_2; bool HelloWorld::init() { f_2 f2 = std::mem_fn(&int_div::int_div_fun); // 指向成員函數(shù) int_div int_div_object; int v = f2(&int_div_object, 5); // 在對(duì)象x上用參數(shù)5調(diào)用X::foo() std::function<int (int)> ff = std::bind( f2, &int_div_object, std::placeholders::_1); // f的第一個(gè)參數(shù)是&x v = ff(5); // 調(diào)用x.foo(5) }
ps:被vs2012的bug給坑了。因?yàn)榭淳W(wǎng)上的代碼于是剛開(kāi)始第9行是這么寫(xiě)的:f_2 f2 = &int_div::int_div_fun;
然后就報(bào)錯(cuò)誤:Error 1 error C2664: 'std::_Func_class<_Ret,_V0_t,_V1_t>::_Set' : cannot convert parameter 1 from '_Myimpl *' to 'std::_Func_base<_Rx,_V0_t,_V1_t> *'
查了一下,vs2010沒(méi)有這個(gè)編譯錯(cuò)誤,但是2012有。2012必須得加上std::mem_fn才能編譯。
可以用function取代函數(shù)指針。因?yàn)樗梢员4婧瘮?shù)延遲執(zhí)行,所以比較適合作為回調(diào)函數(shù),也可以把它看做類(lèi)似于c#中特殊的委托,只有一個(gè)成員的委托。
struct int_div { // 構(gòu)造一個(gè)可以使用"()"進(jìn)行調(diào)用的函數(shù)對(duì)象類(lèi)型 float operator() (int x, int y) const { return ((float)x)/y; }; int int_div_fun( int x ){ return x; }; int_div( std::function<void()>& f ):m_callback(f){}; void Notify() { m_callback(); } std::function<void()> m_callback; };
function還可以作為函數(shù)入?yún)?,這樣可以在函數(shù)外部控制函數(shù)的內(nèi)部行為了,讓我們的函數(shù)變得更加靈活。
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函數(shù)外面更改f的行為 f = std::bind(H, std::placeholders::_1); Foo(4, f); }
c++11中推出function是為了泛化函數(shù)對(duì)象,函數(shù)指針,引用函數(shù),成員函數(shù)的指針,讓我們可以按更統(tǒng)一的方式寫(xiě)出更加泛化的代碼;推出bind是為了替換和增強(qiáng)之前標(biāo)準(zhǔn)庫(kù)的bind1st和bind2st,讓我們的用起來(lái)更方便
到此這篇關(guān)于C++ 11 std::function和std::bind使用詳解的文章就介紹到這了,更多相關(guān)C++ 11 std::function和std::bind內(nèi)容請(qǐng)搜素腳本之家以前的文章或下面相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言中結(jié)構(gòu)體struct編寫(xiě)的一些要點(diǎn)解析
這篇文章主要介紹了C語(yǔ)言中結(jié)構(gòu)體struct編寫(xiě)的一些要點(diǎn)解析,談到了結(jié)構(gòu)體的聲明和指針指向等重要知識(shí)點(diǎn),需要的朋友可以參考下2016-04-04舉例講解C語(yǔ)言程序中對(duì)二叉樹(shù)數(shù)據(jù)結(jié)構(gòu)的各種遍歷方式
這篇文章主要介紹了舉例講解C語(yǔ)言程序中對(duì)二叉樹(shù)數(shù)據(jù)結(jié)構(gòu)的各種遍歷方式,先序中序后序二叉樹(shù)遍歷幾乎成了最老生常談的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)知識(shí),的朋友可以參考下2016-04-04基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易三子棋游戲
這篇文章主要為大家詳細(xì)介紹了基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易三子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下<BR>2022-01-01使用C++實(shí)現(xiàn)簡(jiǎn)單的文章生成器
這篇文章主要為大家詳細(xì)介紹了鵝湖使用C++實(shí)現(xiàn)簡(jiǎn)單的狗屁不通文章生成器,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以了解下2024-03-03C++中實(shí)現(xiàn)子進(jìn)程執(zhí)行和管道通信詳解
在這篇博客中,我們將深入探索如何在 C++ 程序中實(shí)現(xiàn)子進(jìn)程的創(chuàng)建與執(zhí)行,以及父子進(jìn)程間的管道通信,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01C++函數(shù)三種傳參形式(指針傳遞、引用傳遞、值傳遞)
不論是哪種參數(shù)傳遞方式,都有形參和實(shí)參之分,本文主要介紹了C++函數(shù)三種傳參形式(指針傳遞、引用傳遞、值傳遞),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03C語(yǔ)言 數(shù)據(jù)結(jié)構(gòu)中求解迷宮問(wèn)題實(shí)現(xiàn)方法
這篇文章主要介紹了C語(yǔ)言 數(shù)據(jù)結(jié)構(gòu)中求解迷宮問(wèn)題實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2017-03-03基于C語(yǔ)言char與unsigned char的區(qū)別介紹
本篇文章小編為大家介紹,基于C語(yǔ)言char與unsigned char的區(qū)別介紹。需要的朋友參考下2013-04-04Visual Studio Code (vscode) 配置 C / C++ 環(huán)境的流程
這篇文章主要介紹了Visual Studio Code (vscode) 配置 C / C++ 環(huán)境的流程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09