From 7d191847ecdcd0bd5eaf293336ec7bcb0c1a56e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E8=80=B3=E5=A0=80=E5=B7=9D=E9=9B=B7=E9=BC=93?= <58223265+neko-horikawaraiko@users.noreply.github.com> Date: Sun, 28 Feb 2021 14:40:40 +0800 Subject: [PATCH] Update item23.md --- 5.RRefMovSemPerfForw/item23.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/5.RRefMovSemPerfForw/item23.md b/5.RRefMovSemPerfForw/item23.md index c48549c..17559c5 100644 --- a/5.RRefMovSemPerfForw/item23.md +++ b/5.RRefMovSemPerfForw/item23.md @@ -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(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`只对绑定了右值的引用进行到右值转换。这是两种完全不同的动作。前者是典型地为了移动操作,而后者只是传递(亦为转发)一个对象到另外一个函数,保留它原有的左值属性或右值属性。因为这些动作实在是差异太大,所以我们拥有两个不同的函数(以及函数名)来区分这些动作。