【问题标题】:C++ lambda by-value capture semantics and allowed optimizationsC++ lambda 按值捕获语义和允许的优化
【发布时间】:2018-07-25 02:10:17
【问题描述】:

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

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 大小的可见外部影响,但我不知道在规范中的哪里可以找到允许哪些优化的答案.

【问题讨论】:

  • 请注意,as-if 规则始终适用:即使捕获了所有a,编译器也只需确保程序的输出与理论输出匹配,因此它可以决定不复制a.z
  • 我对 as-if 规则在复制 lambdas 时如何运作特别模糊,因为捕获的元素可能具有可见的 ctor/dtor 副作用等。我有一半的预期是这里的大多数优化是允许的,但我怀疑会禁止部分复制的交互集相当大和微妙。
  • 好吧,如果捕获元素会有可见的副作用,那么这些副作用必然是捕获的结果。如果您的A 具有非默认复制构造函数,则必须调用该复制构造函数。如果编译器可以证明在执行构造函数之后不需要简单地复制捕获的类的一些其他成员,那么编译器就不需要经历实际这样做的动作。但它仍然必须调用复制构造函数,“好像”整个实例是按值捕获的。
  • std::array&lt;bool,N&gt; 没有任何可见的副作用

标签: c++ c++11 lambda


【解决方案1】:

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    相关资源
    最近更新 更多