首页 > 解决方案 > C++ lambda 按值捕获语义和允许的优化

问题描述

当函子仅使用隐式捕获对象的某些数据成员时,编译器允许从按值默认捕获中省略什么?例如,

struct A {
  // some members we care about:
  char x;
  int y;
  // some huge amount of state we do not:
  std::array<bool, 200000> z;

  int foo() const { return y + 1 }
};

void bar() {
  A a;
  // must the entirety of a be copy captured, or is the compiler allowed to pick/prune?
  auto l1 = [=](){ std::cout << a.x << ", " << a.y << std::endl; };
  // ...
}

同样,何时允许早期评估忽略更广泛的捕获?

void baz(int i) {
  A a2;
  a2.y = i;

  // capture fundamentally only needs 1 int, not all of an A instance.
  auto l2 = [=](){ std::cout << a.foo() << std::endl; }
}

至少在某些情况下,对元素进行部分和完整的复制捕获应该没有超出 lambda 大小的可见外部影响,但我不知道在规范中的何处寻找允许优化的答案。

标签: c++c++11lambda

解决方案


我认为,原则上,编译器将被允许以一种仅捕获 as-if 规则下已使用成员的副本的方式对此进行优化。[expr.prim.lambda] §2的相关部分:

[…] 一个实现可以定义不同于下面描述的闭包类型,前提是这不会改变程序的可观察行为,除非改变:

  • 闭包类型的大小和/或对齐方式,
  • 闭包类型是否可以轻松复制,或者
  • 闭包类型是否是标准布局类。

然而,在对闭包类型的快速测试检查中sizeof(),似乎没有一个主要的编译器(clang、gcc、msvc)以这种方式优化闭包类型本身。

但是,应该注意的是,只有当您实际将从 lambda 表达式获得的对象存储在某个地方(例如,在 中std::function)时,这才真正成为一个问题。通常情况下,lambda 表达式的结果将简单地用作某个函数模板的参数,然后被丢弃。在这种情况下,所有内容最终都被内联,优化器应该(并且在我的测试中这样做)只是丢弃生成的代码,用于复制从未引用的数据......</p>


推荐阅读