【问题标题】:What is the motivation behind C++11 lambda expressions?C++11 lambda 表达式背后的动机是什么?
【发布时间】:2010-06-10 21:21:37
【问题描述】:

我试图找出在 C++ 中使用 lambda 表达式是否有实际的计算优势,即 “由于我们使用 lambda 表达式,此代码编译/运行更快/更慢” 或者它只是一个整洁的开发特权,可以被试图看起来很酷的可怜的程序员滥用?

我知道这个问题可能看起来很主观,但我非常感谢社区对此问题的意见。

【问题讨论】:

  • 我会以 500 美元的价格拍《被排斥的中间人》。
  • 所以您能想到的语言功能的唯一两个原因是“它运行得更快”或“它就像水稻火箭上的快速条纹”? “缺乏想象力的证明”又是什么花哨的拉丁词?
  • 这很有启发性。谢谢大家的回复。
  • 比什么快/慢?

标签: c++ lambda c++11


【解决方案1】:

好处是编写计算机程序时最重要的事情是:更容易理解代码。我不知道有任何性能考虑。

C++ 在一定程度上允许进行函数式编程。考虑一下:

std::for_each( begin, end, doer );

这个问题是函数(对象)doer

  1. 在循环中指定做了什么
  2. 但在某种程度上隐藏了实际所做的事情(您必须查找函数对象的 operator() 的实现)
  3. 必须在与std::for_each 调用不同的范围内定义
  4. 包含一定数量的样板代码
  5. 通常是丢弃的代码,除了这个循环结构之外什么都不使用

Lambda 在所有这些方面都得到了显着改进(也许还有一些我忘记了)。

【讨论】:

    【解决方案2】:

    我认为计算性能与提高语言的表达能力几乎没有关系。

    【讨论】:

    • 没有 lambda 的编程语言就像没有阳光的一天。
    • @Brian,你住在西雅图,应该不会有太大变化;)
    • @Brian:那么带有 lambda 的 C++ 有点像住在地下室,但有一台电视机显示阳光照在乡村景观上——你看到了光,但它只是没有感觉不一样。
    【解决方案3】:

    本身并没有性能优势,但对 lambda 的需求来自于 STL 及其设计理念的广泛采用。

    具体来说,STL 算法经常使用函子。如果没有 lambda,这些函子需要事先声明才能使用。 Lambda 使得拥有“匿名”的就地函子成为可能。

    这很重要,因为在很多情况下您只需要使用一次仿函数,并且您不想给它命名有两个原因:您不想污染命名空间,并且在那些具体情况,你给的名字要么含糊,要么太长。

    例如,我经常使用 STL,但在没有 C++0x 的情况下,我使用的 for() 循环比 for_each() 算法及其表亲要多得多。那是因为如果我要使用 for_each() 代替,我需要从循环内部获取代码并为其声明一个仿函数。此外,循环之前的所有局部变量都无法访问,因此我需要编写额外的代码将它们作为参数传递给仿函数构造函数,或者其他等效的东西。因此,除非有强烈的动机,否则我倾向于不使用 for_each(),否则代码会更长,更难阅读。

    这很糟糕,因为众所周知,使用 for_each() 和类似的算法为编译器和库提供了更多的优化空间,包括自动并行性。因此,间接地,lambda 将偏爱更高效的代码。

    【讨论】:

    • 迄今为止我发现的最好的“C++ lambda in a nutshell”解释!
    • 哦,所以...就是这样!我想知道为什么所有 lambda 代码示例都是基于 STL 的。鉴于我尽可能避免使用 STL,我无法从 lambda 中找到任何好处,因为我从未遇到过 STL 可读性问题(这可能是我从第一天开始就避免使用 STL 的原因之一) .
    【解决方案4】:

    IMO,关于 lambda 的最重要的事情是它将相关的代码保持在一起。如果你有这个代码:

    std::for_each(begin, end, unknown_function);
    

    您需要导航到 unknown_function 以了解代码的作用。但是使用 lambda,逻辑可以保持在一起。

    【讨论】:

    • 理想情况下,“unknown_function”的命名应该足够好,让您很好地了解它的作用。
    • @Fred:如果你在使用时就已经有了 lambda 定义,那么读者就不必费心去想这个函数是否命名好,而作者也不必费力不必为只会在一个地方使用的琐碎操作取个好名字。
    • @Jim Lewis:同意。我不认为 lambdas 是一件坏事。但与此同时,我不认为将循环体移动到函子中总是一件坏事。与往常一样,使用最适合您情况的方法。
    • “将循环体移动到函子中”——当然,并非总是如此。但是,如果代码没有在其他地方使用(并且您想表明是这种情况),那么 C++03 中匿名命名空间中的类中 operator() 的样板文件有点无聊;-)跨度>
    【解决方案5】:

    Lambda 是函子类的语法糖,所以不,没有计算优势。至于动机,可能是其他十几种流行的语言中的任何一种?

    有人可能会说它有助于代码的可读性(在使用它的地方将你的仿函数声明为内联)。

    【讨论】:

      【解决方案6】:

      虽然我认为 C++0x 的其他部分是 more important,但 lambda 不仅仅是 C++98 风格函数对象的“语法糖”,因为它们可以捕获上下文,而且它们这样做是通过 name 然后他们可以将这些上下文带到其他地方并执行。这是新的东西,而不是“编译得更快/更慢”的东西。

      #include <iostream>
      #include <vector>
      #include <functional>
      void something_else(std::function<void()> f)
      {
              f(); // A closure! I wonder if we can write in CPS now...
      }
      int main()
      {
              std::vector<int> v(10,0);
              std::function<void ()> f = [&](){ std::cout << v.size() << std::endl; };
      
              something_else(f);
      }
      

      【讨论】:

      • 不,这不是新的。 Lambda 在定义时按名称捕获。完全像带有构造函数的函数对象。因为 lambda 函数对象的语法糖,并且可以作为源到源的重写来实现(有关此类重写的示例,请参见 blogs.msdn.com/b/vcblog/archive/2008/10/28/…)。好吧,由于其他原因,在最近的草案中不再可能了:无状态 lambda 现在必须衰减为函数指针,所以它现在与普通函数对象有点不同,但对于捕获部分来说却不是。
      • @Thomas Petit:具有构造函数的函数对象在定义时没有范围概念。要将 [&] lambda 自动重写为仿函数,必须检查其完整源代码并在 lambda 定义之前检查程序的完整源代码,以解析与 lambda 中使用的名称相关联的类型。不是语法转换。
      • 不是语法转换,而是一种更简洁的语法,用于以前可以完成的操作。因此是语法糖。
      【解决方案7】:

      好吧,比较一下:

      int main () {
          std::vector<int> x = {2, 3, 5, 7, 11, 13, 17, 19};
      
          int center = 10;
          std::sort(x.begin(), x.end(), [=](int x, int y) {
              return abs(x - center) < abs(y - center);
          });
      
          std::for_each(x.begin(), x.end(), [](int v) {
              printf("%d\n", v);
          });
      
          return 0;
      }
      

      用这个:

      // why enforce this to be defined nonlocally?
      void printer(int v) {
          printf("%d\n", v);
      } 
      
      int main () {
          std::vector<int> x = {2, 3, 5, 7, 11, 13, 17, 19};
      
          // why enforce we to define a whole struct just need to maintain a state?
          struct {
              int center; 
              bool operator()(int x, int y) const {
                  return abs(x - center) < abs(y - center);
              }
          } comp = {10};
      
          std::sort(x.begin(), x.end(), comp);
      
          std::for_each(x.begin(), x.end(), printer);
      
          return 0;
      }
      

      【讨论】:

      • 我认为第二个甚至不合法。 AFAIK 函数局部类型不得用作模板参数。还是C++2003解决了这个尴尬的问题?
      • @sbi:模板?什么模板?无论如何,第二部分仍然在 c++0x 模式下编译(注意使用初始化列表),因为 OP 只是询问 lambda 表达式的动机。
      • std::sort()std::for_each() 是 @sbi 引用的模板。不过,C++0x 解除了这一限制。
      【解决方案8】:

      一个整洁的开发福利可以被试图看起来很酷的可怜的程序员滥用?”......不管你怎么称呼它,它使代码很多更具可读性和可维护。它不会提高性能。

      大多数情况下,程序员会迭代一系列元素(搜索元素、累积元素、排序元素等)。使用函数式风格,您可以立即看到程序员打算做什么,这与使用 for 循环不同,后者的一切“看起来”都一样。

      比较算法 + lambda:

      iterator longest_tree = std::max_element(forest.begin(), forest.end(), [height]{arg0.height>arg1.height});
      iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), []{is_leaf(arg0)});
      std::transform(forest.begin(), forest.end(), firewood.begin(), []{arg0.trans(...));
      std::for_each(forest.begin(), forest.end(), {arg0.make_plywood()});
      

      使用老式的 for 循环;

      Forest::iterator longest_tree = it.begin();
      for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
         if (*it.height() > *longest_tree.height()) {
           longest_tree = it;
         }
      }
      
      Forest::iterator leaf_tree = it.begin();
      for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
         if (it->type() == LEAF_TREE) {
           leaf_tree  = it;
           break;
         }
      }
      
      for (Forest::const_iterator it = forest.begin(), jt = firewood.begin(); 
           it != forest.end(); 
           it++, jt++) {
                *jt = boost::transformtowood(*it);
          }
      
      for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
          std::makeplywood(*it);
      }
      

      (我知道这段代码包含语法错误。)

      【讨论】:

        猜你喜欢
        • 2021-12-24
        相关资源
        最近更新 更多