1. 關于std::function()
在C語言的時代,我們可以使用函數指針來吧一個函數作為參數傳遞,這樣我們就可以實現回調函數的機制。到了C++11以后在標準庫里引入了std::function模板類,這個模板概括了函數指針的概念
函數指針只能指向一個函數,而std::function對象可以代表任何可以調用的對象,比如說任何可以被當作函數一樣調用的對象。
當你創建一個函數指針的時候,你必須定義這個函數簽名(表征這個函數的入參,返回值等信息);同樣的,當你創建一個std::function對象的時候,你也必須指定它所代表的可調用對象的函數簽名。這一點可以通過std::function的模板參數來實現。
舉個例子來說,如果要定義一個std::function對象func,這個對象可以表示任何有如下函數簽名的可調用對象的,
bool(const std::unique_ptr<Widget>)&, // C++11里面用來比較兩個
const std::unique_ptr<Widget>&) //std::unique_ptr<Widget>對象的函數簽名
你可以這么寫,
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)> func;
這是因為lambda表達式產生了可調用的對象,這個對象這里稱做一個閉包(closure),可以保存在std::function對象里面。
closure(閉包)的定義是,一個函數和它所引用的非本地變量(非lambda表達式內部定義的變量)的一個集合。
2. 使用std::function作為函數入參
2.1 基于傳值的方式傳遞參數
參看下面一段代碼,實現了一個注冊回調函數的機制,
#include <fonctional>
void registerCallBack(std::function<void()>);
入參std::function<void()>是一個模板類對象,它可以用一個函數簽名為void()的可調用對象來進行初始化;上述實現里面是一個傳值調用。我們來看一下它的調用過程,
// 方法(A)
registerCallBack([=]{
.... // 回調函數的實現部分
})
這里使用了lambda表達式作為函數的入參,正如前面所說的lambda表達式會生成一個匿名的閉包(closure),基于這個閉包構造了一個std::function<void()>的對象,然后通過傳值調用的方式把這個對象傳遞registerCallBack函數中使用。
2.2 基于引用的方式傳遞參數
當然我們還可以如下實現這個注冊函數,入參通過const引用的方式傳遞,這里的引用必須是const的,這是因為調用registerCallBack函數的地方生成了一個臨時的std::function()對象,是一個右值,否則編譯會報錯。
//方法(B)
void registerCallBack(std::function<void()> const&)
這兩者的區別就在于,在registerCallBack函數內部怎么使用這個入參,如果只是簡單的調用一下std::func()類,那么兩種都沒有問題,可能使用引用的效率更高;如果register函數內部需要保存這個std::func(),并用于以后使用,那么方法A直接保存沒有問題,方法B就必須做一次拷貝,否則方法B中,當臨時的對象銷毀時,有可能出現引用懸空的問題。
2.3 傳值方式下的std::function對象保存
如果我們要在registerCallBack函數內部保存這個傳入的function對象,我們可以使用轉移操作std::move,這樣的效率更高,
class CallBackHolder
{
public:
void registerCallBack(std::function<void()> func)
{
callback = std::move(func);
}
private:
std::function<void()> callback;
}
3. 類的成員函數作為函數入參
類的成員函數都會默認有個隱藏的this指針,所以不像普通的函數直接作為入參就可以了。
3.1 使用std::bind()和std::function來實現
std::function是通用的多態函數封裝器,它的實例可以存儲、復制以及調用任何可以調用的目標:函數,lambda表達式/bind表達式或其他函數對象,還有指向成員函數指針和指向數據成員指針;
std::bind接受一個函數(或者函數對象),生成一個重新組織的函數對象;
看下面一個例子,classA提供了一個注冊函數,用來注冊一個回調函數
class classA
{
typedef std::function<void(int i)> callback_t;
...
void registCb(callback_t func)
{cbHandle = std::move(func);}
private:
callback_t cbHandle;
};
另一個類classB需要注冊自己的一個成員函數作為回調函數到classA中,這里就可以使用std::bind函數來實現,
class classB
{
public:
classB(classA& cA)
{
cA.registCb(std::bind(&classB::handle, this, std::placeholders::_1));
}
};
- bind函數中顯示的傳遞classB的this指針作為第一個參數給回調函數;
- std::placeholders:_1代表一個占位符,用于回調函數顯式的入參;
Effective Modern C++中專門有一節解釋過std::bind的方式比較繁瑣,并且有時侯會有一些局限性,所以在引入了lambda表達式后就可以用lambda表達式來替代std::bind實現函數回調注冊。
3.2 使用lambda表達式實現
使用lambda表達式的方式可以簡化這一個過程,參看如下一段代碼,classB注冊一個成員函數作為回調函數到classA中,classA會保存這個回調函數(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表達式中捕獲了classB的this指針
- 使用std::move的方式保存function對象到classA中