C++?如何將Lambda轉換成函數(shù)指針
沒有捕獲任何變量的Lambda表達式可以轉換成與它的調用原型一致的函數(shù)指針。
參考下面的代碼
void example1() { ? ? auto add = [](int x, int y) ? ? { ? ? ? ? return x + y; ? ? }; ? ? int x = 2, y = 3; ? ? int z1 = add(x, y); ? ? ? ? ? ? ? ? ?// 調用Lambda ? ? int(*f)(int, int) = add; ? ? ? ? ? ? // Lambda轉換成函數(shù)指針 ? ? int z2 = f(x, y); ? ? ? ? ? ? ? ? ? ?// 調用函數(shù) ? ? cout << z1 << ", " << z2 << endl; }
Lambda是實現(xiàn)了函數(shù)調用運算符的匿名類(anonymous class)。對于每一個Lambda,編譯器創(chuàng)建匿名類,并定義相應的數(shù)據成員存儲Lambda捕獲的變量。沒有捕獲變量的Lambda不包含任何含成員變量。
一個沒有任何成員變量(包括沒有虛函數(shù)表指針)的類型,在空指針上調用成員函數(shù)也不會有任何的問題,因為它的成員函數(shù)不會通過this指針訪問內存。
當Lambda向函數(shù)指針的轉換時,編譯器為Lambda的匿名類實現(xiàn)函數(shù)指針類型轉換運算符。
上面的例子中,編譯器實現(xiàn)operator int(*)(int, int)。
下面是Visual C++編譯器為語句int(*f)(int, int) = add生成的64位匯編代碼:
lea ? ? ? ? rcx,[add] ? call ? ? ? ?<lambda_0e153cdea67ea404383c23c1022dd325>::operator int (__cdecl*)(int,int)? mov ? ? ? ? qword ptr [f],rax ?
第1行,變量"add"的地址存入rcx寄存器;第2行,調用匿名類的函數(shù)指針類型轉換運算符;第3行,返回結果存入變量f。
提示:在默認的成員函數(shù)調用約定__thiscall下,this指針通過rcx寄存器傳遞, 有關__thiscall的詳細內容,請參考:https://docs.microsoft.com/en-us/cpp/cpp/thiscall。
下面是類型轉換運算符實現(xiàn)的一行關鍵匯編代碼
lea ? ? ? ? rax,[<lambda_0e153cdea67ea404383c23c1022dd325>::<lambda_invoker_cdecl>]?
這一行把匿名類的名為lambda_invoker_cdecl的函數(shù)地址存入用于返回結果的寄存器rax。因為只有類的靜態(tài)函數(shù)可以轉換成非成員函數(shù)指針,所以lambda_invoker_cdecl是靜態(tài)函數(shù)。下面是此函數(shù)其中一段匯編代碼:
xor ? ? ? ? rcx,rcx ? call ? ? ? ?<lambda_0e153cdea67ea404383c23c1022dd325>::operator()?
第一行,rcx寄存器清0,即this指針置0;第二行,調用operator()。
綜合上面的分析,得出Lambda轉換成函數(shù)指針的一種可能的實現(xiàn)方式,參考下面的代碼:
typedef int(*FUNCADD)(int, int); // 實現(xiàn)兩個整數(shù)相加的函數(shù)對象 class add_function_object { public: ? ? // 函數(shù)調用運算符 ? ? int operator()(int x, int y)const ? ? { ? ? ? ? return x + y; ? ? } ? ? // 函數(shù)指針類型轉換運算符 ? ? operator FUNCADD()const ? ? { ? ? ? ? return add; ? ? } private: ? ? static int add(int x, int y) ? ? { ? ? ? ? add_function_object* obj = nullptr; ? ? ? ? return obj->operator()(x, y); ? ? } }; void example2() { ? ? int x = 2, y = 3; ? ? add_function_object add; ? ? int z1 = add(x, y); ? ? auto fadd = add.operator FUNCADD(); ? ? int z2 = fadd(x, y); ? ? cout << z1 << ", " << z2 << endl; }
從C++17起,沒有捕獲任何變量的Lambda可以用作值類型模板實參,參考下面的代碼:
typedef int (*INTEGER_OPERATION)(int, int); int do_integer_operation(INTEGER_OPERATION op, int x, int y) { ? ? return op(x, y); } template <INTEGER_OPERATION op> int integer_operation_t(int x, int y) { ? ? return op(x, y); } void example3() { ? ? auto add = [](int x, int y) ? ? { ? ? ? ? return x + y; ? ? }; ? ? auto sub = [](int x, int y) ? ? { ? ? ? ? return x - y; ? ? }; ? ? int z1 = integer_operation_t<add>(2, 3); ? ? int z2 = do_integer_operation(integer_operation_t<sub>, 2, 3); ? ? cout << "z1 : " << z1 << ", z2 : " << z2 << endl; }
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。