【问题标题】:Can a C++11 lambda capture a file scope variable?C++11 lambda 可以捕获文件范围变量吗?
【发布时间】:2014-06-29 20:30:06
【问题描述】:

ISO C++11 规范的第 5.1.2 p10 节规定:

使用常规规则查找捕获列表中的标识符 用于非限定名称查找 (3.4.1);每个这样的查找都会找到一个 具有在到达中声明的自动存储持续时间的变量 本地 lambda 表达式的范围。一个实体(即一个变量或 如果 this) 出现在 lambda-expression 的捕获列表。

这似乎意味着 lambda 无法捕获文件范围变量。例如,这个程序应该是非法的:

#include <iostream>

int x = 13;

int main()
{
  auto l = [](){ return x; };

  std::cout << l() << std::endl;

  return 0;
}

但是,g++ 4.7.1 产生了我期望的结果:

$ g++ --version
g++ (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ -std=c++11 lambda.cpp 
$ ./a.out 
13

但是clang 3.0 崩溃了:

$ clang --version
Ubuntu clang version 3.0-6ubuntu3 (tags/RELEASE_30/final) (based on LLVM 3.0)
Target: i386-pc-linux-gnu
Thread model: posix
$ clang -std=c++11 lambda.cpp 
0  libLLVM-3.0.so.1 0xb70a59e8
1  libLLVM-3.0.so.1 0xb70a5f34
2                   0xb775d400 __kernel_sigreturn + 0
3  clang            0x0869f2e9 clang::Sema::DeduceAutoType(clang::TypeSourceInfo*, clang::Expr*, clang::TypeSourceInfo*&) + 73
<snip>

我的程序是否非法?如果是非法的,那么禁止捕获文件范围变量的理由是什么?

【问题讨论】:

  • 你没有在你的例子中捕获x,你只是使用它。毕竟它是一个全球性的(所以你可以使用它,因为它是可见且可访问的)。见this question
  • "如果是非法的,禁止捕获文件范围变量的理由是什么?"您只需要捕获自动变量,所以你只能可以捕获自动变量。见stackoverflow.com/a/13827928/420683
  • 感谢您的解释!如果您将您的 cmets 合并到一个简短的答案中,我会接受它。
  • clang 崩溃只是因为 lambda 支持在 clang 中直到 3.1 才完成。这在已完成 lambda 支持的版本中运行良好。

标签: c++ c++11 lambda


【解决方案1】:

出于名称查找的目的,lambda 的主体被认为是在 lambda 表达式的上下文中。也就是说,名称查找就像您在 lambda 之外使用名称一样。见[expr.prim.lambda]/7。例如:

#include <iostream>

int x = 13;
int y = 0;

int main()
{
  static int y = 42;
  int z = 1729;

  auto l = [/*forget about the capture for a moment*/]()
  { return x+y+z; };
  // find the names x,y,z as if they had been mentioned outside the lambda
  // find the locals y, z and the global x

  std::cout << l() << std::endl;

  return 0;
}

现在,您需要捕获自动存储持续时间的变量。我想这使它不太容易出错,因为您可以复制并返回 lambda,以便在调用 lambda 时自动变量已被销毁:

int main()
{
  static int y = 42;

  std::function<int()> f;
  {
    int z = 1729;

    f = [](){ return x+y+z; }; // imagine we could do this
  }

  std::cout << f() << std::endl; // uh-oh!

  return 0;
}

当然,对于具有静态存储时长的变量,这个问题不会出现。

具体来说,[expr.prim.lambda]/12 说:

如果是 lambda 表达式 或函数调用的实例化 通用 lambda odr-uses (3.2) this 或 从其到达范围内具有自动存储持续时间的变量,即 实体应被 lambda-expression 捕获。

非自动变量也会通过名称查找找到,但不受此规则影响。您可以在不捕获的情况下使用它们。


注意odr-use 放宽允许使用自动变量而不捕获它们,例如:

int main()
{
  int x = 42;
  constexpr int y = 1789;

  auto l = []() -> int
  {
      decltype(x) my_var = 100;  // no odr-use of `x`
      return my_var*y;           // no odr-use of `y`
  };

  std::cout << l() << std::endl;

  return 0;
}

【讨论】:

  • 实际上,只要捕获是按价值计算的,您的“哦”就可以了。我认为当您可以通过添加一个字符来显式捕获所有内容时,标准没有理由费心指定默认值。
  • @SteveJessop 是的,我指的是不捕获z,类似于不捕获xy。标准可以允许访问自动变量而不捕获它们,但这会导致生命周期问题(以及与引用对象的位置有关的等效实现问题)。因此,他们需要被捕获,然后有几种方法可以做到这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-13
  • 1970-01-01
  • 2012-12-25
  • 1970-01-01
  • 2021-02-07
  • 1970-01-01
  • 2015-12-05
相关资源
最近更新 更多