编译器是否足够聪明,std :: move变量超出范围?

时间:2018-06-16 00:24:33

标签: c++ compiler-optimization rvalue nrvo

考虑以下代码:

std::vector<int> Foo() {
    std::vector<int> v = Bar();
    return v;
}

return v是O(1),因为NRVO会省略副本,构建v directly in the storage where the function's return value would otherwise be moved or copied to。现在考虑功能类似的代码:

void Foo(std::vector<int> * to_be_filled) {
    std::vector<int> v = Bar();
    *to_be_filled = v;
}

这里可以进行类似的论证,因为*to_be_filled = v可以编译为O(1)move-assign,因为它是一个超出范围的局部变量(对于编译器来说应该很容易)验证v在这种情况下没有外部引用,从而在最后一次使用时将其提升为右值。是这样的吗?是否有一个微妙的原因?

此外,感觉这种模式可以扩展到左值超出范围的任何上下文:

void Foo(std::vector<int> * to_be_filled) {
  if (Baz()) {
    std::vector<int> v = Bar();
    *to_be_filled = v;
  }
  ...
}

期望编译器能够找到*to_be_filled = v之类的模式然后自动优化它们以假设rvalue语义吗?/ / /是否有用/合理?

编辑:

g ++ 7.3.0 在-O3模式下执行任何此类优化。

1 个答案:

答案 0 :(得分:9)

编译器不允许任意决定将左值名称转换为要移动的右值。它只能 这样做,而C ++标准允许它这样做。例如在return语句中(仅当其return <identifier>;时)。

*to_be_filled = v;将始终执行副本。即使它是可以访问v的最后一个语句,它也始终是副本。编译器不允许更改。

  

我的理解是返回v是O(1),因为NRVO(实际上)将v变成rvalue,然后使用std :: vector的move-constructor。

这不是它的工作原理。 NRVO将彻底消除移动/复制。但return <identifier>;成为右值的能力不是“优化”。它实际上是一个要求,编译器将它们视为rvalues。

编译器可以选择复制省略。编译器无法选择return <identifier>;做什么。所以上面要么根本不移动(如果发生NRVO),要么移动对象。

  

为什么不能有一个微妙的原因?

这是不允许的一个原因是因为语句的位置不应随意改变该语句的作用。请注意,return <identifier>;将始终从标识符移动(如果它是局部变量)。它在函数中的位置并不重要。由于是return语句,我们知道如果return被执行,则不会执行任何操作。

对于任意陈述,情况并非如此。表达式*to_be_filled = v;的行为不应该根据它在代码中的位置而改变。您不应该仅仅因为在函数中添加另一行而将移动转换为副本。

另一个原因是任意陈述很快就会变得非常复杂。 return <identifier>;非常简单;它将标识符复制/移动到返回值并返回。

相比之下,如果您引用了v,并且to_be_filled以某种方式使用了return <identifier>;,会发生什么。当然,在你的情况下不可能发生,但其他更复杂的情况呢?可以想象,最后一个表达式可以从对移动对象的引用中读取。

create-react-app个案例中要做到这一点要困难得多。