1. 關(guān)于std::function()
在C語言的時代具则,我們可以使用函數(shù)指針來吧一個函數(shù)作為參數(shù)傳遞砾淌,這樣我們就可以實(shí)現(xiàn)回調(diào)函數(shù)的機(jī)制啦撮。到了C++11以后在標(biāo)準(zhǔn)庫里引入了std::function模板類,這個模板概括了函數(shù)指針的概念
函數(shù)指針只能指向一個函數(shù)汪厨,而std::function對象可以代表任何可以調(diào)用的對象赃春,比如說任何可以被當(dāng)作函數(shù)一樣調(diào)用的對象。
當(dāng)你創(chuàng)建一個函數(shù)指針的時候骄崩,你必須定義這個函數(shù)簽名(表征這個函數(shù)的入?yún)⑵噶郏祷刂档刃畔ⅲ┍「ǎ煌瑯拥模?dāng)你創(chuàng)建一個std::function對象的時候抠璃,你也必須指定它所代表的可調(diào)用對象的函數(shù)簽名站楚。這一點(diǎn)可以通過std::function的模板參數(shù)來實(shí)現(xiàn)。
舉個例子來說搏嗡,如果要定義一個std::function對象func窿春,這個對象可以表示任何有如下函數(shù)簽名的可調(diào)用對象的,
bool(const std::unique_ptr<Widget>)&, // C++11里面用來比較兩個
const std::unique_ptr<Widget>&) //std::unique_ptr<Widget>對象的函數(shù)簽名
你可以這么寫采盒,
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)> func;
這是因?yàn)閘ambda表達(dá)式產(chǎn)生了可調(diào)用的對象旧乞,這個對象這里稱做一個閉包(closure),可以保存在std::function對象里面磅氨。
closure(閉包)的定義是尺栖,一個函數(shù)和它所引用的非本地變量(非lambda表達(dá)式內(nèi)部定義的變量)的一個集合。
2. 使用std::function作為函數(shù)入?yún)?/h1>
2.1 基于傳值的方式傳遞參數(shù)
參看下面一段代碼烦租,實(shí)現(xiàn)了一個注冊回調(diào)函數(shù)的機(jī)制延赌,
#include <fonctional>
void registerCallBack(std::function<void()>);
入?yún)td::function<void()>是一個模板類對象,它可以用一個函數(shù)簽名為void()的可調(diào)用對象來進(jìn)行初始化叉橱;上述實(shí)現(xiàn)里面是一個傳值調(diào)用挫以。我們來看一下它的調(diào)用過程,
// 方法(A)
registerCallBack([=]{
.... // 回調(diào)函數(shù)的實(shí)現(xiàn)部分
})
這里使用了lambda表達(dá)式作為函數(shù)的入?yún)⑶宰#缜懊嫠f的lambda表達(dá)式會生成一個匿名的閉包(closure)掐松,基于這個閉包構(gòu)造了一個std::function<void()>的對象,然后通過傳值調(diào)用的方式把這個對象傳遞registerCallBack函數(shù)中使用粪小。
2.2 基于引用的方式傳遞參數(shù)
當(dāng)然我們還可以如下實(shí)現(xiàn)這個注冊函數(shù)大磺,入?yún)⑼ㄟ^const引用的方式傳遞,這里的引用必須是const的探膊,這是因?yàn)檎{(diào)用registerCallBack函數(shù)的地方生成了一個臨時的std::function()對象量没,是一個右值,否則編譯會報錯突想。
//方法(B)
void registerCallBack(std::function<void()> const&)
這兩者的區(qū)別就在于,在registerCallBack函數(shù)內(nèi)部怎么使用這個入?yún)⒕孔ィ绻皇呛唵蔚恼{(diào)用一下std::func()類猾担,那么兩種都沒有問題,可能使用引用的效率更高刺下;如果register函數(shù)內(nèi)部需要保存這個std::func()绑嘹,并用于以后使用,那么方法A直接保存沒有問題橘茉,方法B就必須做一次拷貝工腋,否則方法B中姨丈,當(dāng)臨時的對象銷毀時,有可能出現(xiàn)引用懸空的問題擅腰。
2.3 傳值方式下的std::function對象保存
如果我們要在registerCallBack函數(shù)內(nèi)部保存這個傳入的function對象蟋恬,我們可以使用轉(zhuǎn)移操作std::move,這樣的效率更高趁冈,
class CallBackHolder
{
public:
void registerCallBack(std::function<void()> func)
{
callback = std::move(func);
}
private:
std::function<void()> callback;
}
3. 類的成員函數(shù)作為函數(shù)入?yún)?/h1>
類的成員函數(shù)都會默認(rèn)有個隱藏的this指針歼争,所以不像普通的函數(shù)直接作為入?yún)⒕涂梢粤恕?/p>
3.1 使用std::bind()和std::function來實(shí)現(xiàn)
std::function是通用的多態(tài)函數(shù)封裝器,它的實(shí)例可以存儲渗勘、復(fù)制以及調(diào)用任何可以調(diào)用的目標(biāo):函數(shù)沐绒,lambda表達(dá)式/bind表達(dá)式或其他函數(shù)對象,還有指向成員函數(shù)指針和指向數(shù)據(jù)成員指針旺坠;
std::bind接受一個函數(shù)(或者函數(shù)對象)乔遮,生成一個重新組織的函數(shù)對象;
看下面一個例子取刃,classA提供了一個注冊函數(shù)蹋肮,用來注冊一個回調(diào)函數(shù)
class classA
{
typedef std::function<void(int i)> callback_t;
...
void registCb(callback_t func)
{cbHandle = std::move(func);}
private:
callback_t cbHandle;
};
另一個類classB需要注冊自己的一個成員函數(shù)作為回調(diào)函數(shù)到classA中,這里就可以使用std::bind函數(shù)來實(shí)現(xiàn)蝉衣,
class classB
{
public:
classB(classA& cA)
{
cA.registCb(std::bind(&classB::handle, this, std::placeholders::_1));
}
};
- bind函數(shù)中顯示的傳遞classB的this指針作為第一個參數(shù)給回調(diào)函數(shù)括尸;
- std::placeholders:_1代表一個占位符,用于回調(diào)函數(shù)顯式的入?yún)ⅲ?/li>
Effective Modern C++中專門有一節(jié)解釋過std::bind的方式比較繁瑣病毡,并且有時侯會有一些局限性濒翻,所以在引入了lambda表達(dá)式后就可以用lambda表達(dá)式來替代std::bind實(shí)現(xiàn)函數(shù)回調(diào)注冊。
3.2 使用lambda表達(dá)式實(shí)現(xiàn)
使用lambda表達(dá)式的方式可以簡化這一個過程啦膜,參看如下一段代碼有送,classB注冊一個成員函數(shù)作為回調(diào)函數(shù)到classA中,classA會保存這個回調(diào)函數(shù)(std::function對象)到成員變量中僧家,用于后面使用雀摘,
#include <iostream>
#include <functional>
#include <memory>
class classA
{
typedef std::function<void(int i)> callback_t;
public:
classA() {}
~classA() {}
void handle(int i)
{
std::cout << "classA::handle" << std::endl;
cbHandle(i);
}
void registCb(callback_t func)
{cbHandle = std::move(func);}
private:
callback_t cbHandle;
};
class classB
{
public:
classB(classA& cA)
{
cA.registCb([this](int i){classB::handle(i);});
}
~classB() {}
void handle(int i)
{
std::cout << "classB, handle message" << i << std::endl;
}
};
int main()
{
classA testa;
classB testb(testa);
testa.handle(10);
}
- lambda表達(dá)式中捕獲了classB的this指針
- 使用std::move的方式保存function對象到classA中