11

考虑以下代码:

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

return v是 O(1),因为 NRVO 将省略复制,v 直接在存储中构造函数的返回值,否则函数的返回值将被移动或复制到. 现在考虑功能类似的代码:

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

可以在这里提出一个类似的论点,*to_be_filled = v可以想象它可以编译为 O(1) 移动分配,因为它是一个超出范围的局部变量(编译器应该很容易验证其中v没有外部引用这种情况,因此在最后一次使用时将其提升为右值)。是这样吗?有一个微妙的原因吗?

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

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

期望编译器找到诸如 the 之类的模式*to_be_filled = v,然后自动优化它们以假设右值语义,是否/可以/是否有用/合理?


编辑:

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

4

1 回答 1

12

不允许编译器任意决定将左值名称转换为要从中移动的右值。它只能在 C++ 标准允许的情况下这样做。例如在一个return声明中(并且仅当它的return <identifier>;)。

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

我的理解是 return v 是 O(1),因为 NRVO 将(实际上)将 v 变成一个右值,然后使用 std::vector 的移动构造函数。

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

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

有一个微妙的原因吗?

不允许这样做的一个原因是,语句的位置不应随意更改该语句正在执行的操作。看,return <identifier>;将始终从标识符移动(如果它是局部变量)。它在函数中的位置无关紧要。由于是一个return语句,我们知道如果return执行了,它之后的任何内容都不会被执行。

这不是任意陈述的情况。表达式的行为*to_be_filled = v;不应根据它在代码中的位置而改变。您不应该仅仅因为您在函数中添加了另一行而将移动变为副本。

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

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

在某些情况下要做到这一点要困难得多return <identifier>;

于 2018-06-16T00:29:51.287 回答