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

Are compilers clever enough to std::move variables going out of scope?

本文关键字:变量 move 范围 std 以至于 是否 编译器      更新时间:2023-10-16

考虑以下代码段:

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;
}
...
}

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


编辑:

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

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

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

我的理解是返回 v 是 O(1(,因为 NRVO 将(实际上(将 v 变成一个 rvalue,然后利用 std::vector 的移动构造函数。

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

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

有没有一个微妙的原因不呢?

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

武断的陈述并非如此。表达式的行为不应*to_be_filled = v;根据它在代码中的位置而更改。您不应该仅仅因为向函数添加了另一行而将移动转换为副本。

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

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

return <identifier>;的情况下,要做到这一点要困难得多。