【问题标题】:C++0x lambdas coding styleC++0x lambdas 编码风格
【发布时间】:2010-11-15 06:50:29
【问题描述】:

我想知道人们如何在编码风格方面使用 C++0x lambda。最有趣的问题是在编写捕获列表时要做到多彻底。一方面,该语言允许显式列出捕获的变量,并且通过“显式优于隐式规则”,因此进行详尽的列表以清楚地说明意图是有意义的。例如:

 int sum;
 std::for_each(xs.begin(), xs.end(), [&sum](int x) { sum += x });

对此的另一个论点是,由于 ref-captured locals 的生命周期不会仅仅因为它们被捕获而改变(因此 lambda 很容易最终引用一个生命周期早已结束的本地),因此使捕获显式有助于减少此类错误并追踪它们。

另一方面,该语言还特意提供了一种快捷方式来自动捕获所有引用的本地变量,因此很明显它是打算使用的。并且可以声称,对于上述示例,即使使用自动捕获也会发生什么,并且 lambda 的生命周期不会超过周围的范围,因此没有理由不使用它:

 int sum;
 std::for_each(xs.begin(), xs.end(), [&](int x) { sum += x });

显然,这不一定是非全有或全无,但必须有一些理由来决定何时自动捕获以及何时明确地进行捕获。有什么想法吗?

另一个同样的问题是何时使用按复制捕获 - [=],何时使用按引用捕获 - [&]。 Capture-by-copy 显然更安全,因为没有生命周期问题,因此有人可能会争辩说,只要不需要改变捕获的值(或从其他地方查看对其所做的更改),就应该默认使用它,并捕获 -在这种情况下,引用应被视为(可能为时过早的)优化,仅在明显产生影响的情况下应用。

另一方面,按引用捕获几乎总是更快(特别是因为它通常可以优化到一个副本,如果后者实际上更快,对于小类型和可内联模板函数,例如大多数 STL 算法) ,并且如果 lambda 永远不会超出其范围(这也是所有 STL 算法的情况),那么它是安全的,因此在这种情况下默认为按引用捕获是一种微不足道且无害的优化,不会造成伤害。

你有什么想法?

【问题讨论】:

  • 我对那些已经在生产代码中使用过 lambdas 的人的反馈特别感兴趣,因此有一些经验来支持他们的观点;但欢迎所有反馈。
  • Lambda 还没有真正在许多编译器中实现 - 我怀疑在 G++ 或 MSVC++ 至少支持它们之前你会找到很多生产代码
  • 我实际上正在编写大量使用 VC10 lambdas 的生产代码(尤其是在较新的部分),但后来我是 VS2010 团队的开发人员,所以我猜我的情况有点特别。 .. 但是,我的印象是较新的g++ 版本(不是测试版!)具有生产质量的 lambda 实现 - 我弄错了吗?
  • 据我所知,G++ 还没有 lambdas。一堆其他 0x 功能,但不是 lambdas。
  • GCC 在当前的 4.4 版本和开发中的 4.5 版本中都没有 lambda。有关 GCC 的 C++0x 功能支持的详细信息,请参阅gcc.gnu.org/projects/cxx0x.html

标签: c++ coding-style lambda c++11


【解决方案1】:

我从未听说过“显式规则优于隐式规则”的规则,我也不同意。当然,在某些情况下它是正确的,但也有很多不是这样的情况。这就是为什么 0x 毕竟使用 auto 关键字添加类型推断的原因。 (以及为什么在可能的情况下已经推断出函数模板参数)有很多情况下隐式更可取。

我还没有真正使用过 C++ lambdas(除了使用 VC10 测试版),但我大部分时间都会使用后者

std::for_each(xs.begin(), xs.end(), [&](int x) { sum += x });

我的推理?为什么不这样做?这很方便。有用。而且更容易维护。当我修改 lambda 的主体时,我不必更新捕获列表。为什么我要明确一些编译器比我更了解的东西?编译器可以根据实际使用的内容计算出捕获列表。

至于通过引用与价值捕获?我会应用与常规函数相同的规则。如果您需要引用语义,请按引用捕获。如果您需要复制语义,请执行此操作。如果两者都可以,则更喜欢小类型的价值,如果复制成本高,请参考。

这似乎与您在设计常规函数时必须做出的选择没有什么不同。

我可能应该阅读 lambdas 的规范,但这不是显式捕获列表的主要原因,以便您可以按值捕获一些变量并通过引用捕获其他变量?

【讨论】:

  • 没有明确的理由,但是是的,只有通过捕获列表才有可能。
  • 关于“编译器比我更清楚” - 编译器不知道 lambda 的生命周期。如果您在外部范围内将[&] lambda 分配给std::function,它会很乐意让您这样做,然后离开 lambda 范围 - 但是,当然,当您实际调用 lambda 时,所有引用都将无效...
  • “显式优于隐式”来自 The Zen of Python (python.org/dev/peps/pep-0020),但这并不是 Python 特有的。
  • 啊,我以为我以前在什么地方听过。从来没有听说过它是一个独立的规则。但就像几乎任何规则一样,你可以太从字面上理解它。 Python 中也有很多隐含的内容。 (在某些情况下,它也与以下行相矛盾——“简单胜于复杂”。)我认为该规则在解决歧义方面很有用(如果有歧义,不要依赖晦涩的隐含规则来解决它,要明确你想要什么),但是当你的意思很清楚时,没有必要明确说明这一点。
【解决方案2】:

我最初的直觉是,按值捕获提供或多或少与 Java 的匿名内部类相同,后者是一个已知数量。但是,当您希望封闭范围可变时,不要使用 array-of-size-1 技巧,而是可以通过引用捕获。然后,您有责任将 lambda 的持续时间限制在引用范围内。

实际上,我同意您的观点,即在处理算法时,通过引用捕获应该是默认设置,我预计这将是大多数用途。 Java 中匿名内部类的一个常见用途是侦听器。在 C++ 中可以看到的侦听器样式接口较少,因此它的需求较少,但仍然存在。在这种情况下,最好严格按照价值捕获,以避免出错的机会。 shared_ptr 的按值捕获会是一个大习惯吗?

但是,我还没有使用过 lambda,所以我很可能错过了一些重要的东西。

【讨论】:

  • 应该注意的是,C++ 标准库中没有侦听器模式主要是由于它的体积小(最明显的是没有 UI)。几乎任何 UI 工具包都具有某种形式的侦听器/事件,其中一些(Gtk--,IIRC)允许您将任意函数对象注册为侦听器,而不仅仅是函数或方法(例如 Qt)。随着采用 std::tr1::function(在 C++0x 中为 std::function)作为执行此操作的标准方法,这可能会变得更加流行。所以问题就在那里。
  • 关于按值捕获 shared_ptr 的捕获状态应该能够超过范围(例如侦听器) - 好点,事实上我们的代码中有一些已经。
  • @Pavel Minaev:你不需要捕获shared_ptr。您可以按值捕获变量(因此它超出范围),然后声明 lambda mutable,这将允许它改变按值捕获的变量
  • @newacct 仅当捕获的变量是可复制的(例如,您不能以这种方式捕获unique_ptr)并且它未被多个具有状态共享的 lambda 捕获时,按值捕获才有效需要两者之间的语义。
【解决方案3】:

我可以在这里看到一个新的编码标准规则! ;)

这有点做作,但只是为了强调明确的“优势”,请考虑以下几点:

void foo (std::vector<int> v, int x1)
{
  int sum = 0;
  std::for_each (v.begin ()
    , v.end ()
    , [&](int xl) { sum += x1; } 
}

现在,我故意为此选择了不好的名字等,但这只是为了说明这一点。如果我们使用显式捕获列表,那么上面的代码将无法编译,但目前可以。

在非常严格的环境(安全至关重要)中,我可以看到这样的规则是编码标准的一部分。

【讨论】:

  • 虽然你说的是真的,但如果你写了一个 for 循环,你可能会犯完全相同的错误,并且做了 int xl = *iterator;总和 += x1;。在这种情况下,没有人要求明确捕获。在 lambda 的生命周期仅限于本地范围的情况下,我认为隐式捕获并不比简单大括号继承周围的自动范围这一事实更(或更少)危险。当 lambda 将继续存在时,我认为您想要明确捕获的内容(并推断未捕获的内容可以安全地在词法范围结束时消失)。
  • 错误是否存在于其他构造中并不重要。这是 lambda 的另一个优点,因为您可以比“for 循环”更能降低名称的可见性。名称隐藏/可见性有多种方式会导致细微的错误。编码标准的一个目标是减少可能导致问题的情况,您会在 JSF++、MISRA C/C++ 等标准中找到一些关于名称隐藏和减少名称可见性的规则。我可以看到这是另一个。
  • 但与等效的仿函数相比,它实际上并没有降低可见性,它只是防止它被扩展。这不是优势,而是缓解。所以在这一点上,我认为 lambdas (非常轻微)比函子差,因为如果你认为默认捕获不好,你现在必须禁止它并监管禁令。但是,是的,也许比结构化编程更好。
  • 事实上,如果我们看到auto nextblock = [&amp;somevar] {thing tmpvar; do_something(somevar,tmpvar); }; nextblock(); 优先于{thing tmpvar; do_something(somevar,tmpvar);},那么我将完全相信,由于限制了除 somevar 之外的所有本地变量的可见性,lambda 提供了优于现有构造的真正优势。但我相信他们的优势在别处......
  • "...与等效函子相比,降低了可见性"。我从未暗示它会,但是,隐式捕获会增加在 lambda 体内不必要地可见的名称的数量(与仿函数的情况相比) - 我相信这可能被认为是“坏事(tm)”,我可以想象这样一个关于隐式捕获的规则,尤其是在隐式行为通常不受欢迎的安全关键环境中。也许我们应该同意不同意! :)
【解决方案4】:

如果方便的话,我会使用显式捕获列表,当您想要捕获大量变量时(您可能做错了什么并且)您可以使用全部抓取[&amp;] 捕获列表。

我的看法是,显式捕获列表是理想的,应该避免隐式变体,只有这样人们才不必在实际需要时输入大量代码。

【讨论】:

    【解决方案5】:

    我正在阅读以下链接以更好地了解 C++ lambda。示例中使用的编码风格非常简洁,我能够遵循: http://uint32t.blogspot.com/2009/05/using-c0x-lambda-to-replace-boost-bind.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-27
      • 1970-01-01
      • 2010-10-20
      • 2010-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-21
      相关资源
      最近更新 更多