diff --git a/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item24.md b/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item24.md index 1b1bb07..b15cc67 100644 --- a/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item24.md +++ b/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item24.md @@ -56,7 +56,7 @@ f(std::move(w)); //传递给f一个右值;参数param的类型会 //Widget&&,即右值引用 ``` -对一个通用引用而言,类型推导是必要的,但是它还不够。声明引用的格式必须正确,并且这种格式是被限制的。它必须是准确的`T&&`。再看看之前我们已经看过的代码示例: +对一个通用引用而言,类型推导是必要的,但是它还不够。声明引用的格式必须正确,并且这种格式是被限制的。它必须是准确的`T&&`。再看看之前我们已经看过的代码示例: ```cpp template @@ -79,7 +79,7 @@ void f(std::vector&& param); //param是一个右值引用 如果你在一个模板里面看见了一个函数参数类型为`T&&`,你也许觉得你可以假定它是一个通用引用。错!这是由于在模板内部并不保证一定会发生类型推导。考虑如下`push_back`成员函数,来自`std::vector`: ```cpp -template > //来自C++标准 +template > //来自C++标准 class vector { public: @@ -98,7 +98,7 @@ template > //来自C++标准 将会导致`std::vector`模板被实例化为以下代码: ```cpp - class vector> + class vector> { public: void push_back(Widget&& x); // 右值引用 @@ -120,9 +120,9 @@ template > //依旧来自C++标准 } ``` -这儿,类型参数(type parameter)`Args`是独立于`vector`的类型参数之外的,所以`Args`会在每次`emplace_back`被调用的时候被推导(Okay,`Args`实际上是一个参数包(parameter pack),而不是一个类型参数,但是为了讨论之利,我们可以把它**当作**是一个类型参数)。 +这儿,类型参数(type parameter)`Args`是独立于`vector`的类型参数之外的,所以`Args`会在每次`emplace_back`被调用的时候被推导(Okay, `Args`实际上是一个参数包(parameter pack),而不是一个类型参数,但是为了讨论之利,我们可以把它**当作**是一个类型参数)。 -虽然函数`emplace_back`的类型参数被命名为`Args`,但是它仍然是一个通用引用,这补充了我之前所说的,通用引用的格式必须是`T&&`。 没有任何规定必须使用名字`T`。举个例子,如下模板接受一个通用引用,但是格式(`type&&`)是正确的,并且参数`param`的类型将会被推导(重复一次,不考虑边缘情况,也即当调用者明确给定参数类型的时候)。 +虽然函数`emplace_back`的类型参数被命名为`Args`,但是它仍然是一个通用引用,这补充了我之前所说的,通用引用的格式必须是`T&&`。 没有任何规定必须使用名字`T`。举个例子,如下模板接受一个通用引用,但是格式(`type&&`)是正确的,并且参数`param`的类型将会被推导(重复一次,不考虑边缘情况,也即当调用者明确给定参数类型的时候)。 ```cpp template //param是通用引用 @@ -143,10 +143,10 @@ void someFunc(MyTemplateType&& param); }; ``` -如果你对位于匿名函数里的`std::forward`反应是"What the ....!", 这只代表着你可能还没有读 Item 33。别担心。在本节,重要的事是匿名函数声明的`auto&&`类型的参数。`func`是一个通用引用,可以被绑定到任何可被调用的对象,无论左值还是右值。`args`是**0个**或者多个通用引用(也就是说,它是个通用引用参数包(a universal reference parameter pack)),它可以绑定到任意数目、任意类型的对象上。 +如果你对位于匿名函数里的`std::forward`反应是"What the ....!", 这只代表着你可能还没有读 Item 33。别担心。在本节,重要的事是匿名函数声明的`auto&&`类型的参数。`func`是一个通用引用,可以被绑定到任何可被调用的对象,无论左值还是右值。`args`是**0个**或者多个通用引用(也就是说,它是个通用引用参数包(a universal reference parameter pack)),它可以绑定到任意数目、任意类型的对象上。 多亏了`auto`类型的通用引用,函数`timeFuncInvocation`可以对**近乎任意**(pretty-much any)函数进行计时。(如果你想知道*任意(any)*和*近乎任意(pretty-much any*的区别,往后翻到 Item 30)。 -牢记整个本小节——通用引用的基础——是一个谎言,uhh,一个“抽象”。隐藏在其底下的真相被称为"**引用折叠(reference collapsing)**",小节Item 28致力于讨论它。但是这个真相并不降低该抽象的有用程度。区分右值引用和通用引用将会帮助你更准确地阅读代码("究竟我眼前的这个`T&&`是只绑定到右值还是可以绑定任意对象呢?"),并且,当你在和你的合作者交流时,它会帮助你避免歧义("在这里我在用一个通用引用,而非右值引用")。它也可以帮助你弄懂Item 25和26,它们依赖于右值引用和通用引用的区别。所以,拥抱这份抽象,陶醉于它吧。就像牛顿的力学定律(本质上不正确),比起爱因斯坦的相对论(这是真相)而言,往往更简单,更易用。所以这份通用引用的概念,相较于穷究引用折叠的细节而言,是更合意之选。 +牢记整个本小节——通用引用的基础——是一个谎言,uhh, 一个“抽象”。隐藏在其底下的真相被称为"**引用折叠(reference collapsing)**",小节Item 28致力于讨论它。但是这个真相并不降低该抽象的有用程度。区分右值引用和通用引用将会帮助你更准确地阅读代码("究竟我眼前的这个`T&&`是只绑定到右值还是可以绑定任意对象呢?"),并且,当你在和你的合作者交流时,它会帮助你避免歧义("在这里我在用一个通用引用,而非右值引用")。它也可以帮助你弄懂Item 25和26,它们依赖于右值引用和通用引用的区别。所以,拥抱这份抽象,陶醉于它吧。就像牛顿的力学定律(本质上不正确),比起爱因斯坦的相对论(这是真相)而言,往往更简单,更易用。所以这份通用引用的概念,相较于穷究引用折叠的细节而言,是更合意之选。 **记住**: diff --git a/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item26.md b/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item26.md index cb4f95f..607c990 100644 --- a/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item26.md +++ b/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item26.md @@ -25,7 +25,7 @@ logAndAdd("Patty Dog"); // pass string literal 在第一个调用中,`logAndAdd`使用变量作为参数。在`logAndAdd`中`name`最终也是通过`emplace`传递给`names`。因为`name`是左值,会拷贝到`names`中。没有方法避免拷贝,因为是左值传递的。 -在第三个调用中,参数`name`绑定一个右值,但是这次是通过"Patty Dog"隐式创建的临时`std::string`变量。在第二个调用总,`name`被拷贝到`names`,但是这里,传递的是一个字符串字面量。直接将字符串字面量传递给`emplace`,不会创建`std::string`的临时变量,而是直接在`std::multiset`中通过字面量构建`std::string`。在第三个调用中,我们会消耗`std::string`的拷贝开销,但是连移动开销都不想有,更别说拷贝的。 +在第三个调用中,参数`name`绑定一个右值,但是这次是通过"Patty Dog"隐式创建的临时`std::string`变量。在第二个调用中,`name`被拷贝到`names`,但是这里,传递的是一个字符串字面量。直接将字符串字面量传递给`emplace`,不会创建`std::string`的临时变量,而是直接在`std::multiset`中通过字面量构建`std::string`。在第三个调用中,我们会消耗`std::string`的拷贝开销,但是连移动开销都不想有,更别说拷贝的。 我们可以通过使用通用引用(参见Item 24)重写第二个和第三个调用来使效率提升,按照Item 25的说法,`std::forward`转发引用到`emplace`。代码如下: @@ -82,7 +82,7 @@ logAndAdd(nameIdx); // error! 在通用引用中的实现中,将`short`类型`emplace`到`std::string`的容器中,发生了错误。所有这一切的原因就是对于`short`类型通用引用重载优先于`int`类型的重载。 -使用通用引用类型的函数在C++中是贪婪函数。他们机会可以精确匹配任何类型的参数(极少不适用的类型在Item 30中介绍)。这也是组合重载和通用引用使用是糟糕主意的原因:通用引用的实现会匹配比开发者预期要多得多的参数类型。 +使用通用引用类型的函数在C++中是贪婪函数。他们几乎可以精确匹配任何类型的参数(极少不适用的类型在Item 30中介绍)。这也是组合重载和通用引用使用是糟糕主意的原因:通用引用的实现会匹配比开发者预期要多得多的参数类型。 一个更容易调入这种陷阱的例子是完美转发构造函数。简单对`logAndAdd`例子进行改造就可以说明这个问题。将使用`std::string`类型改为自定义`Person`类型即可: diff --git a/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item27.md b/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item27.md index 828523f..3a50fa9 100644 --- a/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item27.md +++ b/5.RvalueReferences_MovingSemantics_And_PerfectForwarding/item27.md @@ -203,7 +203,7 @@ public: template< typename T, typename = typename std::enable_if< - !std::is_base_if::type >::value >::type