C++ Lambda 表示式使用詳解
C++ 11 對LB的支援,對於喜歡Functional Programming的人來說,無疑是超好訊息。它使得C++進入了和C#,JavaScript等現代流行的程式設計語言所代表的名人堂。
不熟悉LB本身的網友,可以看MSDN文章
( http://msdn.microsoft.com/en-us/library/dd293608.aspx ),我僅僅簡單地分析一下VC++中LB的用法,實現,和效能。
無名引用
對於一次性的,帶參數列達式,用LB可以節省不必要的class定義和維護,簡化程式的設計-維護代價。
比如下面的vector處理程式碼,簡潔明瞭:
vector<int> v1(10, 1);
int sum = 0;
for_each (v1.begin(), v1.end(), [&](int i){ sum += i; });//Line1
否則,我們必須定義一個function類,把如此簡單的事情複雜化。用了LB,我們把定義function 類的工作,轉交給編譯。VC++中,上述LB編譯的實現是產生一個隱身類:
class _lambda_a01 {
int &capture1_;
public:
_lambda_a01(int &x): capture1_(x) {} //Line2
operator void (int i) { capture1_ += I; }
};
在引用時(Line1),它變成:
_lambda_a01 lbd1(sum);
for(auto a:v1){
ldb1(a);
}
讀者也許好奇,為什麼C++不直接把LB轉換成inline expression (inline 表示式),而是要生成一個隱身類呢?這是因為LB的確可以當成“type”變數來用,這樣使得LB和其他類有了同等地位。比如:
vector<int> v1(10, 1);
int sum = 0;
for_each (v1.begin(), v1.end(), [&](int i){ sum += i; });//Line1
vector<int> v2(10, 1);
int sum2 = 0;
for_each (v1.begin(), v1.end(), [&](int i){ sum2 += i; });//Line2
我們如果用上述的方法,Line1和Line2重複程式碼,是軟體工程的大忌。我們可以用下列LB使用模式:
有名無型引用
vector<int> v1(10, 1);
vector<int> v2(10, 1);
int sum = 0;
auto lb = [&](int i){ sum += i; }; //Line0
for_each (v1.begin(), v1.end(), lb);//Line1
sum = 0; // Line1.1
for_each (v1.begin(), v1.end(), lb});//Line2
在Line0,我們定義了一個有名(lb)無型的LB,可以在Line1和Line2重複使用。
注意的是,
1) 每個LB的“定義”都會產生新的“隱身”類,所以儘量用“有名引用”,會減少程式碼的size,縮小工作集。
2) 定義時,LB一次性“俘獲”環境變數,所以上面修改後的程式碼加了Line1.1,以便正確表達應用邏輯。
3) 俘獲可以是“傳值(by value)”也可以是“傳引用(by reference)。我們Line0用的是by reference.
有名有型引用
上面兩種LB使用模式,是LB應用的主要模式,它直接反映出了LB的優點。另一方面說,既然LB無非是隱身類,我們沒有理由不能把它當作普通變數使用。這個模式是一種簡化的functor使用模式。我們可以把LB定義成一個std::function,比如上面的auto lb可以定義成:
std::function <void(int)> lb; //lb is a function which takes an integer and returns void
注意到用這個定義,使得我們可以推遲給LB變數賦值,甚至一變數賦多址(不同時間)。下面就是一個簡單用例:
struct MyLambda
{
std::function <int (int)> _lbda;//line1
int _extra;
};
MyLambda TestLambdaObj(int t)
{
MyLambda ret;
if (t == 1)
{
ret._extra = t;
ret._lbda = [=](int x) -> int { return t + x; }; //line2
return ret;
}
else
{
ret._extra = t;
ret._lbda = [=](int x) -> int { return t * x; };//line3
return ret;
}
}
void TestLambdaFun2(int t)
{
MyLambda ret = TestLambdaObj(t);
int v = ret._lbda(t); //line4
printf(“v is ‘%d’ for type %d”, v, t);
}
我們先定義MyLambda資料類,並與其定義了一了function成員_lbda,根據C++ SPEC,他可以由LB轉換構造,並且和普通的類變數無甚區別。然後我們可以執行時給它賦值(line2,line3), 當作普通function來使用(line4)。
注意的是:
- function的定義中沒有“閉包”的概念,閉包的形成是在LB建立時實現(line2,line3)。
- 把LB賦值給function變數,必然造成呼叫時(line4)的間接性(通過函式指標),其效能相當於虛擬函式,也不能inline化,當然比直接呼叫有所下降。
閉包(closure)是LB的獨特附加值
如果你問為什用LB而不用std::function?我的回答是“閉包”。
C++用LB來實現閉包,是一個簡化繁瑣的class初始化的syntax sugar。這一點是std::function所不可替代的。比如說:
auto sum = 0;
auto step = 2;
auto lb = [&](int i){ sum += i + step; }//capture sum and step by ref
lb形成自己的閉包,自動從環境中俘獲了sum和step,若用class實現,上面的程式起碼增加10行程式碼。
LB效能初探
下面的簡單程式,測試四種功能完全一樣,但使用不同表示式的邏輯:
1)t =1 時用LB,
2)t=2 時用直接表示式
3)t=3 時用函式
4)t=4時用std::function間接呼叫LB
void TestLambdaFun(int t)
{
using namespace std;
vector<int> v1(10, 1);
int x = 0;
int u = 0;
if (t == 1)
{
clock_t begin = clock();
for (int i = 0; i < 100000; ++i)
{
for_each (v1.begin(),
v1.end(),
[&x, &u](int i){ u += i+(x++); });// Line 1
}
clock_t end = clock();
auto spent = double(end – begin) / CLOCKS_PER_SEC;
printf(“spent for type ‘%d’ is %f u is %d\n”, t, spent, u);
}
else if (t == 2)
{
clock_t begin = clock();
for (int i = 0; i < 100000; ++i)
{
auto _First = v1.begin();
auto _Last = v1.end();
for (; _First != _Last; ++_First)
{
u = *_First+(x++); // Line 2
}
}
clock_t end = clock();
auto spent = double(end – begin) / CLOCKS_PER_SEC;
printf(“spent for type ‘%d’ is %f u is %d\n”, t, spent, u);
}
else if (t == 3)
{
clock_t begin = clock();
for (int i = 0; i < 100000; ++i)
{
auto _First = v1.begin();
auto _Last = v1.end();
for (; _First != _Last; ++_First)
{
FuncAdd(u, x, *_First); // Line 3
}
}
clock_t end = clock();
auto spent = double(end – begin) / CLOCKS_PER_SEC;
printf(“spent for type ‘%d’ is %f u is %d\n”, t, spent, u);
}
else if (t == 4)
{
clock_t begin = clock();
std::function <void (int)> lbda;
for (int i = 0; i < 100000; ++i)
{
lbda = [&](int i){ u += i + (x++); };
for_each (v1.begin(), v1.end(), lbda); // Line 4
}
clock_t end = clock();
auto spent = double(end – begin) / CLOCKS_PER_SEC;
printf(“spent for type ‘%d’ is %f u is %d\n”, t, spent, u);
}
}
void FuncAdd(int &u, int &x, int i)
{
u = i+(x++);
}
下面是VC++ 2010中的測試結果:
- 在debug模式下,t=2時速度最快,這是因為t=1,t=3,t=4時都是用了函式呼叫,效能當然不及inline表示式。
- 在release模式下(選擇/Ob1優化,對inline函式進行inline擴充套件)
- t=1和t=2速度完全一樣,比t=3時平均快3倍。當然,我們也可以把FuncAdd inline化。這裡的主要目的,是證明優化後,LB的效能和表示式完全一樣。證明C++ Lambda expression不是浪得虛名的隱身類的syntax sugar,而是名副其實的“表示式”。
t=4最慢,它和t=3類似。但是由於通過了std::function的虛擬函式表間接呼叫,/Ob1優化失去作用,使它不但要呼叫一個() operator,而且是通過“虛擬表”間接呼叫。所以從效能上說,把LB通過std::function間接使用,失去了LB的效能優勢。
總結
C++ 11 的lambda expression(簡稱LB),在可以保證和inline expression同樣效能的條件下,增加了引數功能和閉包功能,是我們寫出簡潔,明瞭,易維護程式碼的絕佳工具。應用時,為了避免程式碼重複和增加隱身類的數量,可用有名無型的LB變數。LB也可以賦值於std::function,當作函式指標使用,但是效能不及簡單地inline使用。
相關文章
- Lambda表示式詳解
- C# Lambda表示式詳解,及Lambda表示式樹的建立C#
- Java中lambda表示式詳解Java
- Java中Lambda表示式基礎及使用詳解Java
- c++之lambda表示式C++
- 【C++】C++之Lambda表示式C++
- Java8特性詳解 lambda表示式(一):使用篇Java
- 匿名函式(lambda)詳解 C++函式C++
- C++ lambda表示式的通用格式C++
- Kotlin——高階篇(一):Lambda表示式詳解Kotlin
- Java lambda表示式基本使用Java
- Java8特性詳解 lambda表示式(二):流式處理中的lambdaJava
- lambda 表示式
- lambda表示式
- Java中Lambda表示式的使用Java
- Java8特性詳解 lambda表示式(三):原理篇Java
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- java lambda 表示式Java
- Java Lambda表示式Java
- C++ 接受狀態變數的lambda表示式C++變數
- C++ 中的lambda表示式【C++11版本】C++
- [轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式Java
- 如何利用 C++ 的 Lambda 表示式提升 Qt 程式碼C++QT
- JS正規表示式使用詳解JS
- 正規表示式使用詳解(轉)
- Spring EL表示式使用詳解Spring
- Java中使用lambda表示式自定義排序Java排序
- 淺談lambda表示式
- Java之lambda表示式Java
- kotlin lambda表示式Kotlin
- Python - lambda 表示式Python
- Lambda表示式總結
- Java的Lambda表示式Java
- Java 8 Lambda 表示式Java
- C++Lambda表示式C++
- java 8 lambda表示式Java
- Java Lambda 表示式初探Java