c++ 閉包 boost::bind 函數(shù)對象 仿函數(shù)

c++ 閉包 boost::bind 函數(shù)對象 仿函數(shù)

Posted on 2014-12-14 12:20 bw_0927 閱讀(481) 評論(0) 編輯 [收藏](javascript:void(0))

http://microcai.org/2013/07/20/closure.html

可以為類類型的對象重載函數(shù)調(diào)用操作符,定義了調(diào)用操作符的類单刁,其對象稱之為函數(shù)對象(function object),即它們的行為類似函數(shù)對象苇瓣。

仿函數(shù)(Functor剿另、Function Object)

傳遞給STL算法的函數(shù)型參數(shù)(functional arguement)不一定要是函數(shù),可以是行為類似于函數(shù)的對象,即Function Object或者Functor哲虾。

STL中大量運(yùn)用了Function Object,也提供了很多預(yù)先定義的Function Object择示。

在大多數(shù)函數(shù)式語言中束凑,不允許函數(shù)有副作用,即函數(shù)不能訪問或改變外部狀態(tài)(比如全局變量)栅盲,這樣做極大方便了單元測試和bug 定位以及并發(fā)汪诉,但是在一些函數(shù)式語言中對函數(shù)副作用的要求稍稍放寬了限制,

引入了詞法閉包(lexical closure),允許函數(shù)可以保留自己的context, 以便設(shè)計(jì)出傳出值是函數(shù)的函數(shù)谈秫。

C++ 閉包 探秘

Posted on 20 Jul 2013

我經(jīng)常說協(xié)程, 說協(xié)程的時(shí)候又經(jīng)常會提到閉包. 還有我常說, boost::bind 是神器 歸根結(jié)底, 神的是 "閉包"

沒有閉包, 就無法實(shí)現(xiàn) asio 協(xié)程 (注意, 我說的是 ASIO的協(xié)程(stackless ), 并不是通常意義上 setjmp/longjmp 或者 CreateFiber 又或者 boost.context 創(chuàng)建的協(xié)程)(stackfull)

每次使用 bind , 你就創(chuàng)建了一個(gè)閉包.

簡單的來說, 閉包就是帶狀態(tài)的函數(shù)

一個(gè)函數(shù), 帶上了一個(gè)狀態(tài), 就變成了閉包了. 什么叫 "帶上狀態(tài)" 呢? 意思是這個(gè)閉包有屬于自己的變量, 這些個(gè)變量的值是創(chuàng)建閉包的時(shí)候設(shè)置的, 并在調(diào)用閉包的時(shí)候, 可以訪問這些變量.

函數(shù)是代碼, 狀態(tài)是一組變量

將代碼和一組變量捆綁 (bind) , 就形成了閉包

內(nèi)部包含 static 變量的函數(shù), 不是閉包, 因?yàn)檫@個(gè) static 變量不能捆綁. 你不能捆綁不同的 static 變量. 這個(gè)在編譯的時(shí)候已經(jīng)確定了. 閉包的狀態(tài)捆綁, 必須發(fā)生在運(yùn)行時(shí).

閉包的實(shí)現(xiàn)

C++ 里使用閉包有3個(gè)辦法

重載 operator()

因?yàn)殚]包是一個(gè)函數(shù)+一個(gè)狀態(tài), 這個(gè)狀態(tài)通過 隱含的 this 指針傳入. 所以 閉包必然是一個(gè)函數(shù)對象. 因?yàn)槌蓡T變量就是極好的用于保存狀態(tài)的工具, 因此實(shí)現(xiàn) operator() 運(yùn)算符重載, 該類的對象就能作為閉包使用. 默認(rèn)傳入的 this 指針提供了訪問成員變量的途徑.

事實(shí)上, lambda 和 bind 的原理都是這個(gè).

lambda

c++11 里提供的 lambda表達(dá)式就是很好的語法糖. 其本質(zhì)和手寫的函數(shù)對象沒有區(qū)別.

boost::bind/std::bind

標(biāo)準(zhǔn)庫提供的 bind 是更加強(qiáng)大的語法糖, 將手寫需要很多很多代碼的閉包, 濃縮到一行 bind 就可以搞定了.

閉包的用法

閉包是一個(gè)強(qiáng)大的武器, 好好使用能事半功倍

用做回調(diào)函數(shù)

閉包的第一個(gè)用處就是作為回調(diào)函數(shù), 消除 C 語言里回調(diào)函數(shù)常見的 *this 指針.

解耦合

@hyq 對這個(gè)問題這么看的

我寫了一個(gè)用于音頻播放的類扒寄,我把play(int16_t* data, int size)這個(gè)函數(shù)直接交給那些需要播放音頻的模塊

但是,如果我某天想要換另外一個(gè)音頻播放的類孝常,這個(gè)類的接口變成了play(int channel, int16_t* data, int size)旗们,那么我可以用boost::bind將第一個(gè)參數(shù)先行綁定了

用boost::bind可以把不同模塊之間可能不兼容的接口給拼接起來

通過兼容的函數(shù)對象, 而不是函數(shù)指針, 放寬了函數(shù)簽名的要求.

信息隔離

avbot 的驗(yàn)證碼實(shí)現(xiàn)里, 有一個(gè)功能是 報(bào)告驗(yàn)證碼錯(cuò)誤 的功能. 這個(gè)實(shí)現(xiàn)就用到了閉包進(jìn)行信息隔離.

static void vc_code_decoded(boost::system::error_code ec, std::string provider, std::string vccode, boost::function<void()> reportbadvc, avbot & mybot)
{
    BOOST_LOG_TRIVIAL(info) << console_out_str("使用 ") <<  console_out_str(provider) << console_out_str(" 成功解碼驗(yàn)證碼!");

    if (provider == "IRC/XMPP 好友輔助驗(yàn)證碼解碼器")
        mybot.broadcast_message("驗(yàn)證碼已輸入");

    mybot.feed_login_verify_code(vccode, reportbadvc);
    need_vc = false;
}

static void on_verify_code(const boost::asio::const_buffer & imgbuf, avbot & mybot, decaptcha::deCAPTCHA & decaptcha)
{
    const char * data = boost::asio::buffer_cast<const char*>( imgbuf );
    size_t  imgsize = boost::asio::buffer_size( imgbuf );
    std::string buffer(data, imgsize);

    BOOST_LOG_TRIVIAL(info) << "got vercode from TX, now try to auto resovle it ... ...";

    decaptcha.async_decaptcha(
        buffer,
        boost::bind(&vc_code_decoded, _1, _2, _3, _4, boost::ref(mybot))
    );
}

async_decaptcha 的回調(diào)函數(shù) vc_code_decoded 的第四個(gè)參數(shù), 是一個(gè)閉包, 如果驗(yàn)證碼有錯(cuò), 直接調(diào)用這個(gè)閉包就可以完成匯報(bào). 至于回報(bào)所需要的一些相關(guān)信息 如 驗(yàn)證碼提供商 返回的驗(yàn)證碼ID 等等信息, 都已經(jīng)封裝在這個(gè)閉包里了. 用戶無需知道匯報(bào)一個(gè)錯(cuò)誤識別的驗(yàn)證碼到底需要多少信息 , 無需知道. 通過閉包, 驗(yàn)證碼識別代碼將這些信息全部隱藏起來了, 甚至兩一個(gè) HANDLE 都無需提供.

用作協(xié)程

多次調(diào)用能保留狀態(tài)的閉包就可以用于實(shí)現(xiàn)是協(xié)程.

不僅僅要修改狀態(tài), 還要根據(jù)狀態(tài)實(shí)現(xiàn)不同的行

保留狀態(tài)的目的就是要后續(xù)調(diào)用的時(shí)候根據(jù)前面的狀態(tài)選擇不同的執(zhí)行路徑 那么這樣做就是協(xié)程了. 雖然手工編寫狀態(tài)代碼并不難, 但是很麻煩, 繁瑣.

因此 ASIO 爸爸提供了一套強(qiáng)大的宏, 將狀態(tài)機(jī)的實(shí)現(xiàn)給自動化了.

=============

http://developer.51cto.com/art/201002/183522.htm

C++編程語言中,有很多功能都與C語言相通构灸,比如指針的應(yīng)用等等上渴。在這里我們介紹的則是一種類似于函數(shù)指針的C++函數(shù)對象的相關(guān)介紹。C++函數(shù)對象不是函數(shù)指針(有狀態(tài))喜颁。但是稠氮,在程序代碼中,它的調(diào)用方式與函數(shù)指針一樣半开,后面加個(gè)括號就可以了隔披。這是入門級的隨筆,說的是函數(shù)對象的定義寂拆,使用奢米,以及與函數(shù)指針,成員函數(shù)指針的關(guān)系纠永。

C++函數(shù)對象實(shí)質(zhì)上是一個(gè)實(shí)現(xiàn)了operator()--括號操作符--的類鬓长。例如:

  1. class Add
  2. {
  3. public:
  4. int operator()(int a, int b)
  5. {
  6. return a + b;
  7. }
  8. };
  9. Add add; // 定義函數(shù)對象
  10. cout << add(3,2); // 5

函數(shù)指針版本就是:

  1. int AddFunc(int a, int b)
  2. {
  3. return a + b;
  4. }
  5. typedef int (*Add) (int a, int b);
  6. Add add = &AddFunc;
  7. cout << add(3,2); // 5

呵呵,除了定義方式不一樣尝江,使用方式可是一樣的涉波。都是:

  1. cout << add(3,2);

既然C++函數(shù)對象與函數(shù)指針在使用方式上沒什么區(qū)別,那為什么要用函數(shù)對象呢?很簡單啤覆,函數(shù)對象可以攜帶附加數(shù)據(jù)苍日,而指針就不行了。下面就舉個(gè)使用附加數(shù)據(jù)的例子:

  1. class less
  2. {
  3. public:
  4. less(int num):n(num){}
  5. bool operator()(int value)
  6. {
  7. return value < n;
  8. }
  9. private:
  10. int n;
  11. };

使用的時(shí)候:

  1. less isLess(10);
  2. cout << isLess(9) << " " << isLess(12); // 輸出 1 0

這個(gè)例子好象太兒戲了窗声,換一個(gè):

  1. const int SIZE = 5;
  2. int array[SIZE] = { 50, 30, 9, 7, 20};
  3. // 找到小于數(shù)組array中小于10的第一個(gè)數(shù)的位置
  4. int * pa = std::find_if(array, array + SIZE, less(10)); //創(chuàng)建一個(gè)臨時(shí)的函數(shù)對象(暫且叫做tmpObj)作為形參,find_if內(nèi)部依次調(diào)用tmpObj(50), tmpObje(30).....
    // pa 指向 9 的位置
  5. // 找到小于數(shù)組array中小于40的第一個(gè)數(shù)的位置
  6. int * pb = std::find_if(array, array + SIZE, less(40)); 
    // pb 指向 30 的位置

/*

find_if的內(nèi)部偽代碼實(shí)現(xiàn)

template``<``class InputIterator, class Predicate>

InputIterator find_if ( InputIterator first, InputIterator last, Predicate pred )

{

for ( ; first!=last ; first++ ) if ( pred(*first) ) break``;

return first;

}

*/

這里可以看出C++函數(shù)對象的方便了吧相恃?可以把附加數(shù)據(jù)保存在函數(shù)對象中,是函數(shù)對象的優(yōu)勢所在嫌佑。
它的弱勢也很明顯豆茫,它雖然用起來象函數(shù)指針,但畢竟不是真正的函數(shù)指針屋摇。在使用函數(shù)指針的場合中揩魂,它就無能為力了。例如炮温,你不能將函數(shù)對象傳給qsort函數(shù)火脉!因?yàn)樗唤邮芎瘮?shù)指針。

要想讓一個(gè)函數(shù)既能接受函數(shù)指針柒啤,也能接受函數(shù)對象倦挂,最方便的方法就是用模板。如:

  1. template<typename FUNC>
  2. int count_n(int* array, int size, FUNC func)
  3. {
  4. int count = 0;
  5. for(int i = 0; i < size; ++i)
  6. if(func(array[i]))
  7. count ++;
  8. return count;
  9. }

這個(gè)函數(shù)可以統(tǒng)計(jì)數(shù)組中符合條件的數(shù)據(jù)個(gè)數(shù)担巩,如:

  1. const int SIZE = 5;
  2. int array[SIZE] = { 50, 30, 9, 7, 20};
  3. cout << count_n(array, SIZE, less(10)); // 2
  4. 用函數(shù)指針也沒有問題:
  5. bool less10(int v)
  6. {
  7. return v < 10;
  8. }
  9. cout << count_n(array, SIZE, less10); // 2

另外方援,C++函數(shù)對象還有一個(gè)函數(shù)指針無法匹敵的用法:可以用來封裝類成員函數(shù)指針!因?yàn)楹瘮?shù)對象可以攜帶附加數(shù)據(jù)涛癌,而成員函數(shù)指針缺少一個(gè)類實(shí)體(類實(shí)例)指針來調(diào)用犯戏,因此,可以把類實(shí)體指針給函數(shù)對象保存起來拳话,就可以用于調(diào)用對應(yīng)類實(shí)體成員函數(shù)了先匪。

  1. template<typename O>
  2. class memfun
  3. {
  4. public:
  5. memfun(void(O::f)(const char), O* o): pFunc(f), pObj(o){}
  6. void operator()(const char* name)
  7. {
  8. (pObj->*pFunc)(name);
  9. }
  10. private:
  11. void(O::pFunc)(const char);
  12. O* pObj;
  13. };
  14. class A
  15. {
  16. public:
  17. void doIt(const char* name)
  18. { cout << "Hello " << name << "!";}
  19. };
  20. A a;
  21. memfun<A> call(&A::doIt, &a); // 保存 a::doIt指針以便調(diào)用
  22. call("Kitty"); // 輸出 Hello Kitty!

大功告成了,終于可以方便保存成員函數(shù)指針弃衍,以備調(diào)用了呀非。

不過,現(xiàn)實(shí)是殘酷的镜盯。函數(shù)對象雖然能夠保有存成員函數(shù)指針和調(diào)用信息岸裙,以備象函數(shù)指針一樣被調(diào)用,但是速缆,它的能力有限降允,一個(gè)函數(shù)對象定義,最多只能實(shí)現(xiàn)一個(gè)指定參數(shù)數(shù)目的成員函數(shù)指針激涤。

標(biāo)準(zhǔn)庫的mem_fun就是這樣的一個(gè)函數(shù)對象,但是它只能支持0個(gè)和1個(gè)參數(shù)這兩種成員函數(shù)指針。如 int A::func()或void A::func(int)倦踢、int A::func(double)等等送滞,要想再多一個(gè)參數(shù)如:int A::func(int, double),不好意思辱挥,不支持犁嗅。想要的話,只有我們自已寫了晤碘。

而且褂微,就算是我們自已寫,能寫多少個(gè)园爷?5個(gè)宠蚂?10個(gè)?還是100個(gè)(這也太恐怖了)童社?

好在boost庫提供了boost::function類求厕,它默認(rèn)支持10個(gè)參數(shù),最多能支持50個(gè)函數(shù)參數(shù)(多了扰楼,一般來說這夠用了呀癣。但它的實(shí)現(xiàn)就是很恐怖的:用模板部份特化及宏定義,弄了幾十個(gè)模板參數(shù)弦赖,偏特化(編譯期)了幾十個(gè)函數(shù)對象项栏。

C++0x已經(jīng)被接受的一個(gè)提案,就是可變模板參數(shù)列表蹬竖。用了這個(gè)技術(shù)沼沈,就不需要偏特化無數(shù)個(gè)C++函數(shù)對象了,只要一個(gè)函數(shù)對象模板就可以解決問題了案腺。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庆冕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子劈榨,更是在濱河造成了極大的恐慌访递,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件同辣,死亡現(xiàn)場離奇詭異拷姿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)旱函,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門响巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棒妨,你說我怎么就攤上這事踪古。” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵伏穆,是天一觀的道長拘泞。 經(jīng)常有香客問我,道長枕扫,這世上最難降的妖魔是什么陪腌? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮烟瞧,結(jié)果婚禮上诗鸭,老公的妹妹穿的比我還像新娘。我一直安慰自己参滴,他們只是感情好强岸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著卵洗,像睡著了一般请唱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上过蹂,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天十绑,我揣著相機(jī)與錄音,去河邊找鬼酷勺。 笑死本橙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脆诉。 我是一名探鬼主播甚亭,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼击胜!你這毒婦竟也來了亏狰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤偶摔,失蹤者是張志新(化名)和其女友劉穎暇唾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辰斋,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡策州,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宫仗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片够挂。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藕夫,靈堂內(nèi)的尸體忽然破棺而出孽糖,到底是詐尸還是另有隱情枯冈,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布办悟,位于F島的核電站霜幼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏誉尖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一铸题、第九天 我趴在偏房一處隱蔽的房頂上張望铡恕。 院中可真熱鬧,春花似錦丢间、人聲如沸探熔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诀艰。三九已至,卻和暖如春饮六,著一層夾襖步出監(jiān)牢的瞬間其垄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工卤橄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绿满,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓窟扑,卻偏偏與公主長得像喇颁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子嚎货,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容