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()--括號操作符--的類鬓长。例如:
- class Add
- {
- public:
- int operator()(int a, int b)
- {
- return a + b;
- }
- };
- Add add; // 定義函數(shù)對象
- cout << add(3,2); // 5
函數(shù)指針版本就是:
- int AddFunc(int a, int b)
- {
- return a + b;
- }
- typedef int (*Add) (int a, int b);
- Add add = &AddFunc;
- cout << add(3,2); // 5
呵呵,除了定義方式不一樣尝江,使用方式可是一樣的涉波。都是:
- cout << add(3,2);
既然C++函數(shù)對象與函數(shù)指針在使用方式上沒什么區(qū)別,那為什么要用函數(shù)對象呢?很簡單啤覆,函數(shù)對象可以攜帶附加數(shù)據(jù)苍日,而指針就不行了。下面就舉個(gè)使用附加數(shù)據(jù)的例子:
- class less
- {
- public:
- less(int num):n(num){}
- bool operator()(int value)
- {
- return value < n;
- }
- private:
- int n;
- };
使用的時(shí)候:
- less isLess(10);
- cout << isLess(9) << " " << isLess(12); // 輸出 1 0
這個(gè)例子好象太兒戲了窗声,換一個(gè):
- const int SIZE = 5;
- int array[SIZE] = { 50, 30, 9, 7, 20};
- // 找到小于數(shù)組array中小于10的第一個(gè)數(shù)的位置
- 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 的位置 - // 找到小于數(shù)組array中小于40的第一個(gè)數(shù)的位置
- 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ù)對象倦挂,最方便的方法就是用模板。如:
- template<typename FUNC>
- int count_n(int* array, int size, FUNC func)
- {
- int count = 0;
- for(int i = 0; i < size; ++i)
- if(func(array[i]))
- count ++;
- return count;
- }
這個(gè)函數(shù)可以統(tǒng)計(jì)數(shù)組中符合條件的數(shù)據(jù)個(gè)數(shù)担巩,如:
- const int SIZE = 5;
- int array[SIZE] = { 50, 30, 9, 7, 20};
- cout << count_n(array, SIZE, less(10)); // 2
- 用函數(shù)指針也沒有問題:
- bool less10(int v)
- {
- return v < 10;
- }
- 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ù)了先匪。
- template<typename O>
- class memfun
- {
- public:
- memfun(void(O::f)(const char), O* o): pFunc(f), pObj(o){}
- void operator()(const char* name)
- {
- (pObj->*pFunc)(name);
- }
- private:
- void(O::pFunc)(const char);
- O* pObj;
- };
- class A
- {
- public:
- void doIt(const char* name)
- { cout << "Hello " << name << "!";}
- };
- A a;
- memfun<A> call(&A::doIt, &a); // 保存 a::doIt指針以便調(diào)用
- call("Kitty"); // 輸出 Hello Kitty!
大功告成了,終于可以方便保存成員函數(shù)指針弃衍,以備調(diào)用了呀非。
- C++常量引用正確應(yīng)用方法
- C++鏈棧模板應(yīng)用代碼解讀
- C++剪切板常用應(yīng)用技巧分享
- C++ Doxygen實(shí)現(xiàn)功能分享
- C++ sprintf格式化解決方法詳解
不過,現(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ù)對象模板就可以解決問題了案腺。