mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-02-24 18:20:45 +08:00
Update item23.md
This commit is contained in:
parent
fcdd8efa7a
commit
7d191847ec
@ -4,7 +4,7 @@
|
||||
|
||||
当你第一次了解到移动语义(*move semantics*)和完美转发(*perfect forwarding*)的时候,它们看起来非常直观:
|
||||
|
||||
- **移动语义**使编译器有可能用廉价的移动操作来代替昂贵的复制操作。正如复制构造函数和复制赋值操作符给了你控制复制语义的权力,移动构造函数和移动赋值操作符也给了你控制移动语义的权力。移动语义也允许创建只可移动(*move-only*)的类型,例如`std::unique_ptr`, `std::future` 和 `std::thread`。
|
||||
- **移动语义**使编译器有可能用廉价的移动操作来代替昂贵的拷贝操作。正如拷贝构造函数和拷贝赋值操作符给了你控制拷贝语义的权力,移动构造函数和移动赋值操作符也给了你控制移动语义的权力。移动语义也允许创建只可移动(*move-only*)的类型,例如`std::unique_ptr`,`std::future`和`std::thread`。
|
||||
|
||||
- **完美转发**使接收任意数量实参的函数模板成为可能,它可以将实参转发到其他的函数,使目标函数接收到的实参与被传递给转发函数的实参保持一致。
|
||||
|
||||
@ -12,13 +12,13 @@
|
||||
|
||||
你对这些特点越熟悉,你就越会发现,你的初印象只不过是冰山一角。移动语义、完美转发和右值引用的世界比它所呈现的更加微妙。举个例子,`std::move`并不移动任何东西,完美转发也并不完美。移动操作并不永远比复制操作更廉价;即便如此,它也并不总是像你期望的那么廉价。而且,它也并不总是被调用,即使在当移动操作可用的时候。构造“`type&&`”也并非总是代表一个右值引用。
|
||||
|
||||
无论你挖掘这些特性有多深,它们看起来总是还有更多隐藏起来的部分。幸运的是,它们的深度总是有限的。本章将会带你到最基础的部分。一旦到达,C++11的这部分特性将会具有非常大的意义。比如,你会掌握`std::move`和`sd::forward`的惯用法。你能够适应“`type&&`”的歧义性质。你会理解移动操作的令人惊奇的不同表现的背后真相。这些片段都会豁然开朗。在这一点上,你会重新回到一开始的状态,因为移动语义、完美转发和右值引用都会又一次显得直截了当。但是这一次,它们不再使人困惑。
|
||||
无论你挖掘这些特性有多深,它们看起来总是还有更多隐藏起来的部分。幸运的是,它们的深度总是有限的。本章将会带你到最基础的部分。一旦到达,C++11的这部分特性将会具有非常大的意义。比如,你会掌握`std::move`和`std::forward`的惯用法。你能够适应“`type&&`”的歧义性质。你会理解移动操作的令人惊奇的不同表现的背后真相。这些片段都会豁然开朗。在这一点上,你会重新回到一开始的状态,因为移动语义、完美转发和右值引用都会又一次显得直截了当。但是这一次,它们不再使人困惑。
|
||||
|
||||
在本章的这些小节中,非常重要的一点是要牢记形参永远是**左值**,即使它的类型是一个右值引用。比如,假设
|
||||
```c++
|
||||
void f(Widget&& w);
|
||||
```
|
||||
形参`w`是一个左值,即使它的类型是一个**Widget**的右值引用。(如果这里震惊到你了,请重新回顾从本书[简介](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/Introduction.md)开始的关于左值和右值的总览。)
|
||||
形参`w`是一个左值,即使它的类型是一个rvalue-reference-to-`Widget`。(如果这里震惊到你了,请重新回顾从本书[简介](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/Introduction.md)开始的关于左值和右值的总览。)
|
||||
|
||||
## 条款二十三:理解`std::move`和`std::forward`
|
||||
|
||||
@ -44,7 +44,7 @@ move(T&& param)
|
||||
|
||||
我为你们高亮了这段代码的两部分(译者注:高亮的部分为函数名`move`和`static_cast<ReturnType>(param)`)。一个是函数名字,因为函数的返回值非常具有干扰性,而且我不想你们被它搞得晕头转向。另外一个高亮的部分是包含这段函数的本质的转换。正如你所见,`std::move`接受一个对象的引用(准确的说,一个通用引用(universal reference),见[Item24](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item24.md)),返回一个指向同对象的引用。
|
||||
|
||||
该函数返回类型的`&&`部分表明`std::move`函数返回的是一个右值引用,但是,正如[Item28](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item28.md)所解释的那样,如果类型`T`恰好是一个左值引用,那么`T&&`将会成为一个左值引用。为了避免如此,*type trait*(见[Item9](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item9.md))`std::remove_reference`应用到了类型`T`上,因此确保了`&&`被正确的应用到了一个不是引用的类型上。这保证了`std::move`返回的真的是右值引用,这很重要,因为函数返回的右值引用是右值(rvalues)。因此,`std::move`将它的实参为一个右值,这就是它的全部作用。
|
||||
该函数返回类型的`&&`部分表明`std::move`函数返回的是一个右值引用,但是,正如[Item28](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item28.md)所解释的那样,如果类型`T`恰好是一个左值引用,那么`T&&`将会成为一个左值引用。为了避免如此,*type trait*(见[Item9](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item9.md))`std::remove_reference`应用到了类型`T`上,因此确保了`&&`被正确的应用到了一个不是引用的类型上。这保证了`std::move`返回的真的是右值引用,这很重要,因为函数返回的右值引用是右值。因此,`std::move`将它的实参转换为一个右值,这就是它的全部作用。
|
||||
|
||||
此外,`std::move`在C++14中可以被更简单地实现。多亏了函数返回值类型推导(见[Item3](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/1.DeducingTypes/item3.md))和标准库的模板别名`std::remove_reference_t`(见[Item9](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item9.md)),`std::move`可以这样写:
|
||||
|
||||
@ -61,14 +61,14 @@ decltype(auto) move(T&& param) //C++14,仍然在std命名空间
|
||||
|
||||
因为`std::move`除了转换它的实参到右值以外什么也不做,有一些提议说它的名字叫`rvalue_cast`之类可能会更好。虽然可能确实是这样,但是它的名字已经是`std::move`,所以记住`std::move`做什么和不做什么很重要。它只进行转换,不移动任何东西。
|
||||
|
||||
当然,右值本来就是移动操作的侯选者,所以对一个对象使用`std::move`就是告诉编译器,这个对象很适合被移动。所以这就是为什么`std::move`叫现在的名字:更容易指定可以被移动的对象。
|
||||
当然,右值本来就是移动操作的候选者,所以对一个对象使用`std::move`就是告诉编译器,这个对象很适合被移动。所以这就是为什么`std::move`叫现在的名字:更容易指定可以被移动的对象。
|
||||
|
||||
事实上,右值只不过**经常**是移动操作的候选者。假设你有一个类,它用来表示一段注解。这个类的构造函数接受一个包含有注解的`std::string`作为形参,然后它复制该形参到数据成员。假设你了解[Item41](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/8.Tweaks/item41.md),你声明一个值传递的形参:
|
||||
|
||||
```cpp
|
||||
class Annotation {
|
||||
public:
|
||||
explicit Annotation(std::string text); //将会被复制的参数,
|
||||
explicit Annotation(std::string text); //将会被复制的形参,
|
||||
… //如同条款41所说,
|
||||
}; //值传递
|
||||
```
|
||||
@ -89,7 +89,7 @@ public:
|
||||
class Annotation {
|
||||
public:
|
||||
explicit Annotation(const std::string text)
|
||||
:value(std::move(text)) //"move" text到value里;这段代码执行起来
|
||||
:value(std::move(text)) //“移动”text到value里;这段代码执行起来
|
||||
{ … } //并不是看起来那样
|
||||
|
||||
…
|
||||
@ -182,7 +182,7 @@ public:
|
||||
}
|
||||
```
|
||||
|
||||
注意,第一,`std::move`只需要一个函数实参(`rhs.s`),而`std::forward`不但需要一个函数实参(`rhs.s`),还需要一个模板类型实参`std::string`。其次,我们传递给`std::forward`的类型应当是一个non-reference,因为惯例是传递的实参应该是一个右值(见[Item28](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item28.md))。同样,这意味着`std::move`比起`std::forward`来说需要打更少的字,并且免去了传递一个表示我们正在传递一个右值的类型实参。同样,它根绝了我们传递错误类型的可能性,(例如,`std::string&`可能导致数据成员`s`被复制而不是被移动构造)。
|
||||
注意,第一,`std::move`只需要一个函数实参(`rhs.s`),而`std::forward`不但需要一个函数实参(`rhs.s`),还需要一个模板类型实参`std::string`。其次,我们传递给`std::forward`的类型应当是一个non-reference,因为惯例是传递的实参应该是一个右值(见[Item28](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item28.md))。同样,这意味着`std::move`比起`std::forward`来说需要打更少的字,并且免去了传递一个表示我们正在传递一个右值的类型实参。同样,它根绝了我们传递错误类型的可能性(例如,`std::string&`可能导致数据成员`s`被复制而不是被移动构造)。
|
||||
|
||||
更重要的是,`std::move`的使用代表着无条件向右值的转换,而使用`std::forward`只对绑定了右值的引用进行到右值转换。这是两种完全不同的动作。前者是典型地为了移动操作,而后者只是传递(亦为转发)一个对象到另外一个函数,保留它原有的左值属性或右值属性。因为这些动作实在是差异太大,所以我们拥有两个不同的函数(以及函数名)来区分这些动作。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user