在MSVC10 下,將lambda expression 轉換成C 的function ...

文章推薦指數: 80 %
投票人數:10人

不過,在Visual C++ 2010(VC10)的環境下,其實lambda expression 有一些不符合標準的小問題…那就是他不能轉換成C 的function pointer(請 ... 直接觀看文章 OpenMenu 之前已經有在《C++0x:Lambdaexpression》一文中,介紹過C++11/C++0x這個算是滿好用的匿名函式、lambdaexpression了~透過lambdaexpression可以很快地建立一個functionobject,而不用另外宣告一個真正的函式,在很多地方,可以有效地簡化程式的寫法。

不過,在VisualC++2010(VC10)的環境下,其實lambdaexpression有一些不符合標準的小問題…那就是他不能轉換成C的functionpointer(請參考《在C++裡傳遞、儲存函式Part1:FunctionPointer》)、拿來註冊成callbackfunction。

下面是一個簡單的範例: #include  using namespacestd; voidCallFunctionPointer(void(*pFunc)()) { (*pFunc)(); } voidDo() { cout<'to 'void(__cdecl*)(void) 也就是說,VC10沒辦法把lambdaexpression轉換成所需要的functionpointer的形式…這個問題在MicrosoftConnect上也已經有人回報了(連結),微軟的說法是會在下一個版本(VisualStudio11)時修正。

解決方法的實作 那如果現在希望可以解決的話,要怎麼辦呢?在《FixingLambdaexpressionsinVisualStudio2010》這篇文章裡面,提供了一種透過Templatemetaprogramming的機制來做封包,藉此把lambdaexpression轉換為可以被VC10當作functionpointer的一般function。

他的方法的基本概念,就是透過根據傳入的lambdaexpressiob來產生一個特別的struct或class,然後透過裡面的staticmemberfunction和staticmemberdata來做操作。

而接下來的程式碼,就是Heresy根據文章中的方法,針對voidfunc()這種不需要參數、也沒有回傳值的函式,稍作修改後實作出來的結果。

首先,是最主要的templateclass:LambdaWrapper: //theclasstowrapalambdaexpression template class LambdaWrapper { public: static TLambda*pFuncPtr; static voidExec() { (*pFuncPtr)(); } }; //instantiatethestaticmemberdata template TLambda*LambdaWrapper::pFuncPtr=NULL; 它的成員都是static的,只有兩個東西: 用來記錄lambdaexpression的指標、也就是staticmemberdatapFuncPtr。

拿來當functionpointer/callbackfunction用的staticmemberfunctionExec()。

他所做的事就是去執行pFuncPtr這個指標所指到的lambdaexperession。

而由於有static的memberdatapFuncPtr,所以也需要globalscope產生他的實例、並進行初始化。

最後,則是Convert()這個直接輸入lambdaexpression、取得functionpointer的template函式了~ //definethetypeoffunctionpointer typedef void(*FunctionType)(); //getfunctionpointerfromlambdaexpression template FunctionTypeConvert(TLambdarFunc) { static TLambdalf(rFunc); LambdaWrapper::pFuncPtr=&lf; return&LambdaWrapper::Exec; } 首先是先透過typedef定義FunctionType、也就是要回傳的functionpointer的型別。

而在Convert()裡面的第一個動作,就是先建立一個static變數lf、將傳進來的lambdaexpressionrFunc複製一份;而由於lf是static的,所以會一直存在、不會在離開Convert()這個函式時被釋放掉。

(註1) 而接下來,則是去設定LambdaWrapper::pFuncPtr的值、讓他指到剛剛建立出來的lf;然後則是把LambdaWrapper::Exec()這個staticmemberfunction傳回來,當作最後的Functionpointer來用。

如此一來,要使用的話就非常簡單了~只要下面這樣呼叫就可以了! CallFunctionPointer(Convert([](){cout<::Exec()這個函式,然後執行我們所指定的lambdaexpression了~ 所以這樣在把上面的程式寫好後,在使用上是非常方便的~像是glut這類本來需要globalfunction或staticmemberfunction的介面,都可以透過lambdaexpression來作封包,可以寫得更物件導向了~ 不過,由於LambdaWrapper這樣的templateclass還是只能針對特定形式的functionpointer來做轉換,像這邊的例子就只能針對void()的形式,不能用在其他形式的functionpointer;所以如果要對應到不同類型的functionpointer,也就需要寫不同的類別出來了…這點也算是比較討厭的地方。

稍微詳細一點的解釋 這個方法主要是透過class的staticmemberdata來紀錄要執行的function、然後透過staticmemberfunction來當作呼叫的介面;但是class裡面的staticmemberdata基本上是共用的,這邊這樣設計,重複使用的時候難道不會在後面呼叫Convert()時被覆蓋掉嗎?(註2) 實際上,這個方法之所以可以這樣用,主要是因為編譯器在處理的時候,每一個lambdaexpression的型別都是不同的!下面就是一個簡單的測試例子: #include  #include  using namespacestd; intmain(intargc,char**argv) { autof1=[](){}; autof2=[](){}; cout<] f2is[class`anonymousnamespace'::] 可以發現,兩者其實是不同的(雖然只差在編號)。

而也由於每一個lambdaexpression都是不同的,所以透過lambdaexpression來產生出來的templateclass:LambdaWrapper,實際上也都會是不同型別的!所以針對不同的lambdaexpression、實際上並非使用同一個pFuncPtr、而是各自擁有一份。

而同樣的,Convert()這個template函式,也是針對不同的lambdaexpression、會在編譯階段、產生不同的function實體,而裡面用來複製、保存lambdaexpression的static變數lf,也都是不一樣的~ 而如果這邊改用std::function這種functionobject的話,就會因為型別相同、而出現問題了~像下面的例子,就是同時展示使用lambdaexpression和functionobject的使用: autofa=Convert([](){cout<fo1=[](){cout<fo2=[](){cout<形式的functionobjectfo1、fo2,然後再透過Convert()、產生對應的functionpointerf1、f2。

最後都準備就緒後,則是依序執行這些被產生出來的functionpointer。

而執行的結果,會是: lambdaa lambdab functionobject1 functionobject1 可以發現,直接使用lambdaexpression的話,結果是正確、沒有問題的~但是如果是使用functionobject的物件的話,Convert()和LambdaWrapper都會因為丟進來的參數型別是相同的(都是function),而產使用同一個函式/類別,導致static的變數實際上是用到同一份、而因此有錯誤的結果。

如果在偵錯模式下實際下去看的話,也會發現其實f1和f2這兩個指標,實際上都是指到同一個位址,也就是LambdaWrapper::Exec()這個函式;而此時,LambdaWrapper裡的pFuncPtr,則是指到第一次傳進Convert>()這個函式的functionobject、fo1的複本、也就是Convert>()裡的static變數lf。

所以理所當然的,f1、f2的執行結果會是相同的,都是去呼叫到fo1這個functionobject的副本、輸出「functionobject1」的字樣。

附註: 在《FixingLambdaexpressionsinVisualStudio2010》這篇文章裡面的作法,是沒有去建立傳入的lambdaexpression、也就是rFunc的副本,而是讓pointer直接去指到這個在外部的lambdaexpression。

Heresy沒有這樣做、而是另外去建立一份lambdaexpression的副本的原因,是怕外部的lambdaexpression會隨著生命週期到了而消失,這時候可能在執行時會有問題。

FunctionTypeGen(inti) { autof=[i](){cout< FunctionTypeConvert(TLambda&rFunc) { LambdaWrapper::pFuncPtr=&rFunc; return&LambdaWrapper::Exec; } 執行的時候所呼叫的pFunctPtr會是指到一個已經被釋放掉的記憶體空間;而Heresy測試時雖然還是可以跑,但是lambdaexpression裡面的i的數值已經亂掉了。

不過實際上,LambdaWrapper::pFuncPtr是指到Convert()這個template函式裡面的static變數lf,而lf只有在第一次執行時會被建立、用來複製第一次傳進來的lambdaexpression,所以其實不會有覆蓋的問題,反而是會變成是被第一次執行時的參數給獨佔。

LambdaExpression只有在沒有capture任何變數的時候可以直接轉換成functionpointer,實際上這個時候它會被當成是類似globalfunction的形式;如果有做變數的capture的話,就變變成類似class的memberfucntion的形式、而無法轉換成上述的functionpointer。

例如下面的例子,在gcc和vc11上都會出現編譯錯誤: #include usingnamespacestd; voidCallFunctionPointer(void(*pFunc)()) { (*pFunc)(); } intmain(intargc,char**argv) { intx; CallFunctionPointer([x](){cout<func_arr; for(intn,n<3,++n) func_arr[n]=([=](inti){cout<



請為這篇文章評分?