转载自:  http://www.codeceo.com/article/cpp-lambda.html

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

我们如果用上述的方法,Line1Line2重复代码,是软件工程的大忌。我们可以用下列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,可以在Line1Line2重复使用。

注意的是,

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转换构造,并且和普通的类变量无甚区别。然后我们可以运行时给它赋值(line2line3), 当作普通function来使用(line4)。

注意的是:

  • function的定义中没有“闭包”的概念,闭包的形成是在LB创建时实现(line2line3)。
  • 把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形成自己的闭包,自动从环境中俘获了sumstep,若用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使用。

C++ Lambda 表达式使用详解的更多相关文章

  1. lambda表达式入门详解

    转自 2018-03-02 Sevenvidia 码农翻身 1.什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个"值".   如果你想把"一块代码 ...

  2. Linq之查询表达式语法详解

    1.闲言碎语 由于项目的需要接触到Linq,刚开始有些不适应,好多概念都很模糊.不过经过一段时间的摸索,慢慢地对Linq有了一个更加深入的了解.在此记录一下备忘.      2.查询表达式语法 执行L ...

  3. Java Spring cron表达式使用详解

    Java Spring cron表达式使用详解   By:授客 QQ:1033553122 语法格式 Seconds Minutes Hours DayofMonth Month DayofWeek ...

  4. Java Web----EL(表达式语言)详解

     Java Web中的EL(表达式语言)详解 表达式语言(Expression Language)简称EL,它是JSP2.0中引入的一个新内容.通过EL可以简化在JSP开发中对对象的引用,从而规范页面 ...

  5. C#基础表达式语句详解(上)

    本节内容: 1.表达式的定义: 2.各类表达式概览: 3.语句的定义: 4.语句详解: 1.表达式的定义: 1.1什么是表达式: (定义见下图)各类编程语言对表达式的实现不尽相同,但大体上都符合这个定 ...

  6. python lambda ,map详解

    lambda 匿名函数 # 普通定义函数 def func1(x,y): return x+y # 执行函数 print(func(1,2)) # 如果此函数只调用一次,或者功能简单,此方法就显得笨拙 ...

  7. cron表达式使用详解

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...

  8. 【转】EL表达式 (详解)

    EL表达式      1.EL简介 1)语法结构        ${expression} 2)[]与.运算符      EL 提供.和[]两种运算符来存取数据.      当要存取的属性名称中包含一 ...

  9. JSP 中EL表达式用法详解

    EL 全名为Expression Language EL 语法很简单,它最大的特点就是使用上很方便.接下来介绍EL主要的语法结构: ${sessionScope.user.sex} 所有EL都是以${ ...

随机推荐

  1. python文件读写详解

    # Python3 open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, ...

  2. python连接数据库自动发邮件

    python连接数据库实现自动发邮件 1.运行环境 redhat6 + python3.6 + crontab + Oracle客户端 2.用到的模块  3.操作步骤 (1)安装python3.6参考 ...

  3. AngualJS-leaflet之视图等级缩放

    在http://tombatossals.github.io/angular-leaflet-directive/#!/examples/events 中的则是zoomlevelschange,然后识 ...

  4. SQL的七种连接

    book表: t_book表: 一:inner join  AB共有的. select * from book inner join t_book on book.t_id=t_book.t_id 查 ...

  5. Activiti的分配任务负责人(八)

    1分配任务负责人 1.1 固定分配 在进行业务流程建模时指定固定的任务负责人 在 properties 视图中,填写 Assignee 项为任务负责人.注意事项由于固定分配方式,任务只管一步一步执行任 ...

  6. time,sys,os模块

      1.time模块 a:结构化时间:struct_time:通过time.localtime获取到一个时间对象,通过这个对象得到对象属性 ****localtime()如果没有参数,默认返回是一个时 ...

  7. 微信小程序自定义底部导航栏组件+跳转

    微信小程序本来封装有底部导航栏,但对于想自定义样式和方法的开发者来说,这并不是很好. 参考链接:https://github.com/ljybill/miniprogram-utils/tree/ma ...

  8. HDU 4277 USACO ORZ(DFS暴搜+set去重)

    原题代号:HDU 4277 原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=4277 原题描述: USACO ORZ Time Limit: 5000/1 ...

  9. android平台上AES,DES加解密及问题

    在使用java进行AES加密的时候,会用到如下方法: SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); 但是在andr ...

  10. Intellij IDEA中使用Debug调试详解

    转载:https://www.linuxidc.com/Linux/2017-09/146772.htm   Intellij IDEA中使用Debug调试详解 Debug用来追踪代码的运行流程,通常 ...