mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-03-03 13:50:43 +08:00
Update item25.md
This commit is contained in:
parent
97abb0505b
commit
c6d8297703
@ -191,7 +191,7 @@ Widget makeWidget() //makeWidget的移动版本
|
||||
|
||||
这是错的,因为对于这种优化,标准化委员会远领先于开发者。早就为人认识到的是,`makeWidget`的“拷贝”版本可以避免复制局部变量`w`的需要,通过在分配给函数返回值的内存中构造`w`来实现。这就是所谓的**返回值优化**(*return value optimization*,RVO),这在C++标准中已经实现了。
|
||||
|
||||
对这种好事遣词表达是个讲究活,因为你想只在那些不影响软件外在行为的地方允许这样的**拷贝省略**(copy elision)。对标准中教条的(也可以说是有毒的)絮叨做些解释,这个特定的好事就是说,编译器可能会在按值返回的函数中省略对局部对象的拷贝(或者移动),如果满足(1)局部对象与函数返回值的类型相同;(2)局部对象就是要返回的东西。(适合的局部对象包括大多数局部变量(比如`nakeWidget`里的`w`),还有作为`return`语句的一部分而创建的临时对象。函数形参不满足要求。一些人将RVO的应用区分为命名的和未命名的(即临时的)局部对象,限制了RVO术语应用到未命名对象上,并把对命名对象的应用称为**命名返回值优化**(*named return value optimization*,NRVO)。)把这些记载脑子里,再看看`makeWidget`的“拷贝”版本:
|
||||
对这种好事遣词表达是个讲究活,因为你想只在那些不影响软件外在行为的地方允许这样的**拷贝消除**(copy elision)。对标准中教条的(也可以说是有毒的)絮叨做些解释,这个特定的好事就是说,编译器可能会在按值返回的函数中消除对局部对象的拷贝(或者移动),如果满足(1)局部对象与函数返回值的类型相同;(2)局部对象就是要返回的东西。(适合的局部对象包括大多数局部变量(比如`nakeWidget`里的`w`),还有作为`return`语句的一部分而创建的临时对象。函数形参不满足要求。一些人将RVO的应用区分为命名的和未命名的(即临时的)局部对象,限制了RVO术语应用到未命名对象上,并把对命名对象的应用称为**命名返回值优化**(*named return value optimization*,NRVO)。)把这些记在脑子里,再看看`makeWidget`的“拷贝”版本:
|
||||
|
||||
```cpp
|
||||
Widget makeWidget() //makeWidget的“拷贝”版本
|
||||
@ -212,9 +212,9 @@ return std::move(w);
|
||||
|
||||
返回的已经不是局部对象`w`,而是**`w`的引用**——`std::move(w)`的结果。返回局部对象的引用不满足RVO的第二个条件,所以编译器必须移动`w`到函数返回值的位置。开发者试图对要返回的局部变量用`std::move`帮助编译器优化,反而限制了编译器的优化选项。
|
||||
|
||||
但是RVO就是个优化。编译器不被**要求**省略拷贝和移动操作,及时他们被允许这样做。或许你会疑惑,并担心编译器用拷贝操作惩罚你,因为它们确实可以这样。或者你可能有足够的了解,意识到有些情况很难让编译器实现RVO,比如当函数不同控制路径返回不同局部变量时。(编译器必须产生一些代码在分配的函数返回值的内存中构造适当的局部变量,但是编译器如何确定哪个变量是合适的呢?)如果这样,你可能会愿意以移动的代价来保证不会产生拷贝。那就是,极可能仍然认为应用`std::move`到一个要返回的局部对象上是合理的,只因为可以不再担心拷贝的代价。
|
||||
但是RVO就是个优化。编译器不被**要求**消除拷贝和移动操作,及时他们被允许这样做。或许你会疑惑,并担心编译器用拷贝操作惩罚你,因为它们确实可以这样。或者你可能有足够的了解,意识到有些情况很难让编译器实现RVO,比如当函数不同控制路径返回不同局部变量时。(编译器必须产生一些代码在分配的函数返回值的内存中构造适当的局部变量,但是编译器如何确定哪个变量是合适的呢?)如果这样,你可能会愿意以移动的代价来保证不会产生拷贝。那就是,极可能仍然认为应用`std::move`到一个要返回的局部对象上是合理的,只因为可以不再担心拷贝的代价。
|
||||
|
||||
那种情况下,应用`std::move`到一个局部对象上**仍然**是一个坏主意。C++标准关于RVO的部分表明,如果满足RVO的条件,但是编译器选择不执行拷贝省略,则返回的对象**必须被视为右值**。实际上,标准要求当RVO被允许时,或者实行拷贝省略,或者将`std::move`隐式应用于返回的局部对象。因此,在`makeWidget`的“拷贝”版本中,
|
||||
那种情况下,应用`std::move`到一个局部对象上**仍然**是一个坏主意。C++标准关于RVO的部分表明,如果满足RVO的条件,但是编译器选择不执行拷贝消除,则返回的对象**必须被视为右值**。实际上,标准要求当RVO被允许时,或者实行拷贝消除,或者将`std::move`隐式应用于返回的局部对象。因此,在`makeWidget`的“拷贝”版本中,
|
||||
|
||||
```cpp
|
||||
Widget makeWidget() //同之前一样
|
||||
@ -225,18 +225,18 @@ Widget makeWidget() //同之前一样
|
||||
}
|
||||
```
|
||||
|
||||
编译器要不省略`w`的拷贝,要不函数看成像下面写的一样:
|
||||
编译器要不消除`w`的拷贝,要不函数看成像下面写的一样:
|
||||
|
||||
```cpp
|
||||
Widget makeWidget()
|
||||
{
|
||||
Widget w;
|
||||
…
|
||||
return std::move(w); //把w看成右值,因为不执行拷贝省略
|
||||
return std::move(w); //把w看成右值,因为不执行拷贝消除
|
||||
}
|
||||
```
|
||||
|
||||
这种情况与按值返回函数形参的情况很像。形参们没资格参与函数返回值的拷贝省略,但是如果作为返回值的话编译器会将其视作右值。结果就是,如果代码如下:
|
||||
这种情况与按值返回函数形参的情况很像。形参们没资格参与函数返回值的拷贝消除,但是如果作为返回值的话编译器会将其视作右值。结果就是,如果代码如下:
|
||||
|
||||
```cpp
|
||||
Widget makeWidget(Widget w) //传值形参,与函数返回的类型相同
|
||||
@ -256,7 +256,7 @@ Widget makeWidget(Widget w)
|
||||
}
|
||||
```
|
||||
|
||||
这意味着,如果对从按值返回的函数返回来的局部对象使用`std::move`,你并不能帮助编译器(如果不能实行拷贝省略的话,他们必须把局部对象看做右值),而是阻碍其执行优化选项(通过阻止RVO)。在某些情况下,将`std::move`应用于局部变量可能是一件合理的事(即,你把一个变量传给函数,并且知道不会再用这个变量),但是满足RVO的`return`语句或者返回一个传值形参并不在此列。
|
||||
这意味着,如果对从按值返回的函数返回来的局部对象使用`std::move`,你并不能帮助编译器(如果不能实行拷贝消除的话,他们必须把局部对象看做右值),而是阻碍其执行优化选项(通过阻止RVO)。在某些情况下,将`std::move`应用于局部变量可能是一件合理的事(即,你把一个变量传给函数,并且知道不会再用这个变量),但是满足RVO的`return`语句或者返回一个传值形参并不在此列。
|
||||
|
||||
**请记住:**
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user