mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-02-05 16:51:05 +08:00
fix typos
This commit is contained in:
parent
97e8e13582
commit
fcdbadce98
@ -56,7 +56,7 @@ f(std::move(w)); //传递给f一个右值;参数param的类型会
|
||||
//Widget&&,即右值引用
|
||||
```
|
||||
|
||||
对一个通用引用而言,类型推导是必要的,但是它还不够。声明引用的格式必须正确,并且这种格式是被限制的。它必须是准确的`T&&`。再看看之前我们已经看过的代码示例:
|
||||
对一个通用引用而言,类型推导是必要的,但是它还不够。声明引用的格式必须正确,并且这种格式是被限制的。它必须是准确的`T&&`。再看看之前我们已经看过的代码示例:
|
||||
|
||||
```cpp
|
||||
template <typename T>
|
||||
@ -79,7 +79,7 @@ void f(std::vector<T>&& param); //param是一个右值引用
|
||||
如果你在一个模板里面看见了一个函数参数类型为`T&&`,你也许觉得你可以假定它是一个通用引用。错!这是由于在模板内部并不保证一定会发生类型推导。考虑如下`push_back`成员函数,来自`std::vector`:
|
||||
|
||||
```cpp
|
||||
template <class T,class Allocator = allocator<T>> //来自C++标准
|
||||
template <class T, class Allocator = allocator<T>> //来自C++标准
|
||||
class vector
|
||||
{
|
||||
public:
|
||||
@ -98,7 +98,7 @@ template <class T,class Allocator = allocator<T>> //来自C++标准
|
||||
将会导致`std::vector`模板被实例化为以下代码:
|
||||
|
||||
```cpp
|
||||
class vector<Widget , allocagor<Widget>>
|
||||
class vector<Widget, allocator<Widget>>
|
||||
{
|
||||
public:
|
||||
void push_back(Widget&& x); // 右值引用
|
||||
@ -120,9 +120,9 @@ template <class T,class Allocator = allocator<T>> //依旧来自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 <typename MyTemplateType> //param是通用引用
|
||||
@ -143,10 +143,10 @@ void someFunc(MyTemplateType&& param);
|
||||
};
|
||||
```
|
||||
|
||||
如果你对位于匿名函数里的`std::forward<decltype(blah blah blah)>`反应是"What the ....!", 这只代表着你可能还没有读 Item 33。别担心。在本节,重要的事是匿名函数声明的`auto&&`类型的参数。`func`是一个通用引用,可以被绑定到任何可被调用的对象,无论左值还是右值。`args`是**0个**或者多个通用引用(也就是说,它是个通用引用参数包(a universal reference parameter pack)),它可以绑定到任意数目、任意类型的对象上。
|
||||
如果你对位于匿名函数里的`std::forward<decltype(blah blah blah)>`反应是"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,它们依赖于右值引用和通用引用的区别。所以,拥抱这份抽象,陶醉于它吧。就像牛顿的力学定律(本质上不正确),比起爱因斯坦的相对论(这是真相)而言,往往更简单,更易用。所以这份通用引用的概念,相较于穷究引用折叠的细节而言,是更合意之选。
|
||||
|
||||
|
||||
**记住**:
|
||||
|
@ -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`类型即可:
|
||||
|
||||
|
@ -203,7 +203,7 @@ public:
|
||||
template<
|
||||
typename T,
|
||||
typename = typename std::enable_if<
|
||||
!std::is_base_if<Person,
|
||||
!std::is_base_of<Person,
|
||||
typename std::decay<T>::type
|
||||
>::value
|
||||
>::type
|
||||
|
Loading…
Reference in New Issue
Block a user