1. 程式人生 > 程式設計 >C++ 11 std::function和std::bind使用詳解

C++ 11 std::function和std::bind使用詳解

cocos new 出新的專案之後,仔細閱讀程式碼,才發現了一句3.0區別於2.0的程式碼:

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

2.0內的程式碼用的不是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__,##__VA_ARGS__)
#define CC_CALLBACK_1(__selector__,std::placeholders::_1,##__VA_ARGS__)
#define CC_CALLBACK_2(__selector__,std::placeholders::_2,##__VA_ARGS__)
#define CC_CALLBACK_3(__selector__,std::placeholders::_3,##__VA_ARGS__)

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

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

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

此時不禁要問onTouchesBegan又是啥,為啥不能直接函式指標賦值呢?

看定義就能明白了

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()函式的意義就像它的函式名一樣,是用來繫結函式呼叫的某些引數的。)例如:

int f(int,char,double);
auto ff = bind(f,_1,'c',1.2);   // 繫結f()函式呼叫的第二個和第三個引數,返回一個新的函式物件為ff,它只帶有一個int型別的引數
int x = ff(7);            // f(7,1.2);

引數的繫結通常稱為"Currying"(譯註:Currying---“烹製咖哩燒菜”,此處意指對函式或函式物件進行加工修飾操作),"_1"是一個佔位符物件,用於表示當函式f通過函式ff進行呼叫時,函式ff的第一個引數在函式f的引數列表中的位置。第一個引數稱為"_1",第二個引數為"_2",依此類推。例如:

int f(int,double);
auto frev = bind(f,_3,_2,_1);    // 翻轉引數順序
int x = frev(1.2,7);       // f(7,1.2);

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

我們無法使用bind()繫結一個過載函式的引數,我們必須顯式地指出需要繫結的過載函式的版本:

int g(int);
double g(double);

auto g1 = bind(g,_1);             // 錯誤:呼叫哪一個g() ?
auto g2 = bind( (double(*)(double))g,_1);  // 正確,但是相當醜陋

void H(int a);
//繫結全域性函式
auto f11 = std::bind(H,std::placeholders::_1);
auto的型別實際上是std::function<void(int)>

//繫結帶引數的成員函式
std::function<void (char*,int)> f = std::bind(&ReadHandler::ConnectPreProcess,this,std::placeholders::_1);

//三元函式轉換成一元函式
int f(int,double);
// 繫結f()函式呼叫的第二個和第三個引數,
// 返回一個新的函式物件為ff,它只帶有一個int型別的引數
auto ff = bind(f,‘c',1.2);  
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,std::placeholders::_2 );
  auto result1 = bf2(10,20); ///< same as a.Func(10,20)

  std::function< int(int)> bf3 = std::bind(&HelloWorld::AddFunc,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表示的是函式這個抽象概念。)例如:

typedef std::function<float (int x,int y)> f ;// 構造一個函式物件,它能表示的是一個返回值為float,兩個引數為int,int的函式 
struct int_div {    // 構造一個可以使用"()"進行呼叫的函式物件型別 
  float operator() (int x,int y) const { return ((float)x)/y; };
};

void HelloWorld::testing()
{
  f f1= int_div();          // 賦值 
  auto result3 = f1( 10,2);
}

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

struct int_div {    // 構造一個可以使用"()"進行呼叫的函式物件型別 
  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);      // 指向成員函式

  int_div int_div_object;
  int v = f2(&int_div_object,5); // 在物件x上用引數5呼叫X::foo()
  std::function<int (int)> ff = std::bind( f2,&int_div_object,std::placeholders::_1);  // f的第一個引數是&x
  v = ff(5);        // 呼叫x.foo(5)


}

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 1 from '_Myimpl *' to 'std::_Func_base<_Rx,_V1_t> *'

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

可以用function取代函式指標。因為它可以儲存函式延遲執行,所以比較適合作為回撥函式,也可以把它看做類似於c#中特殊的委託,只有一個成員的委託。

struct int_div { // 構造一個可以使用"()"進行呼叫的函式物件型別 
  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還可以作為函式入參,這樣可以在函式外部控制函式的內部行為了,讓我們的函式變得更加靈活。

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,讓我們的用起來更方便

到此這篇關於C++ 11 std::function和std::bind使用詳解的文章就介紹到這了,更多相關C++ 11 std::function和std::bind內容請搜素我們以前的文章或下面相關文章,希望大家以後多多支援我們!