编译器是否足够聪明 std::move 变量超出范围?
Are compilers clever enough to std::move variables going out of scope?
考虑以下代码:
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) 移动赋值,因为它是一个超出范围的局部变量(对于编译器验证 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
的语句,它也始终是一个副本。不允许编译器更改它。
My understanding is that return v is O(1), since NRVO will (in effect) make v into an rvalue, which then makes use of std::vector's move-constructor.
事情不是这样的。 NRVO 将完全消除 move/copy。但是 return <identifier>;
成为右值的能力不是 "optimization"。这实际上是一个 要求 编译器将它们视为右值。
编译器可以选择复制省略。编译器无法选择 return <identifier>;
的作用。因此,上面的内容要么根本不移动(如果发生 NRVO),要么移动对象。
Is there a subtle reason why not?
不允许这样做的一个原因是语句的位置不应该随意改变语句的作用。看,return <identifier>;
将始终从标识符移动(如果它是局部变量)。它在函数中的位置并不重要。由于是 return
语句,我们 知道 如果 return
被执行,它之后的任何内容都不会被执行。
任意陈述并非如此。表达式 *to_be_filled = v;
的行为不应根据它恰好在代码中的位置而改变。你不应该仅仅因为你在函数中添加了另一行就可以将一个移动变成一个副本。
另一个原因是任意语句会很快变得非常复杂。 return <identifier>;
很简单;它 copies/moves return 值和 returns.
的标识符
相比之下,如果您有对 v
的引用,并且它以某种方式被 to_be_filled
使用,会发生什么情况。当然这不会发生在你的情况下,但其他更复杂的情况呢?可以想象,最后一个表达式可以从对移出对象的引用中读取。
在 return <identifier>;
个案例中要做到这一点要困难得多。
考虑以下代码:
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) 移动赋值,因为它是一个超出范围的局部变量(对于编译器验证 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
的语句,它也始终是一个副本。不允许编译器更改它。
My understanding is that return v is O(1), since NRVO will (in effect) make v into an rvalue, which then makes use of std::vector's move-constructor.
事情不是这样的。 NRVO 将完全消除 move/copy。但是 return <identifier>;
成为右值的能力不是 "optimization"。这实际上是一个 要求 编译器将它们视为右值。
编译器可以选择复制省略。编译器无法选择 return <identifier>;
的作用。因此,上面的内容要么根本不移动(如果发生 NRVO),要么移动对象。
Is there a subtle reason why not?
不允许这样做的一个原因是语句的位置不应该随意改变语句的作用。看,return <identifier>;
将始终从标识符移动(如果它是局部变量)。它在函数中的位置并不重要。由于是 return
语句,我们 知道 如果 return
被执行,它之后的任何内容都不会被执行。
任意陈述并非如此。表达式 *to_be_filled = v;
的行为不应根据它恰好在代码中的位置而改变。你不应该仅仅因为你在函数中添加了另一行就可以将一个移动变成一个副本。
另一个原因是任意语句会很快变得非常复杂。 return <identifier>;
很简单;它 copies/moves return 值和 returns.
相比之下,如果您有对 v
的引用,并且它以某种方式被 to_be_filled
使用,会发生什么情况。当然这不会发生在你的情况下,但其他更复杂的情况呢?可以想象,最后一个表达式可以从对移出对象的引用中读取。
在 return <identifier>;
个案例中要做到这一点要困难得多。