C++ 之std::function() 作為函式引數入口 詳解
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 <functional>
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中
轉自:https://www.jianshu.com/p/c4c84b073413