zoukankan      html  css  js  c++  java
  • 浅谈C++ Lambda 表达式(简称LB)

    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 lbd1sum);

    forauto av1{

       ldb1a);

    }

     

    读者也许好奇,为什么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 ", 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 ", 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 ", 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 ", 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使用。

  • 相关阅读:
    【数论】无平方因子的数
    【图论】【二叉树】以先序字符串方式建立二叉树
    Dev-c++使用方法?Dev-c++怎么用?这里都有啦
    【图论】【二叉树】以括号形式输出二叉树
    【NOIP2016普及组】复赛——魔法阵
    【NOIP2016普及组】复赛——海港
    python爬虫——web前端基础(3)
    python爬虫——web前端基础(2)
    python爬虫——web前端基础(1)
    python 合并两个文件并将合并内容保存在另一个文件中
  • 原文地址:https://www.cnblogs.com/ly8838/p/3969426.html
Copyright © 2011-2022 走看看