Update item25.md

This commit is contained in:
猫耳堀川雷鼓 2021-02-27 16:44:52 +08:00 committed by GitHub
parent 97abb0505b
commit c6d8297703
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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`语句或者返回一个传值形参并不在此列。
**请记住:**