使用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 <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中
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,533評論 1 51
  • C++ lambda表達式與函數對象 lambda表達式是C++11中引入的一項新技術,利用lambda表達式可以...
    小白將閱讀 85,358評論 15 117
  • 函數和對象 1、函數 1.1 函數概述 函數對于任何一門語言來說都是核心的概念。通過函數可以封裝任意多條語句,而且...
    道無虛閱讀 4,614評論 0 5
  • 接著上節 condition_varible ,本節主要介紹future的內容,練習代碼地址。本文參考http:/...
    jorion閱讀 14,818評論 1 5
  • 一直以為到本命年了,實際上卻只有二十三,足夠證明此刻的生活終究還是煩惱多過快活,畢竟能使人忘卻時間的應該都是煩惱,...
    super7777777閱讀 183評論 0 1