Update item27.md

This commit is contained in:
猫耳堀川雷鼓 2021-02-28 22:22:57 +08:00 committed by GitHub
parent 0c370e202e
commit 571a5da6df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -113,13 +113,13 @@ void logAndAddImpl(int idx, std::true_type) //译者注高亮std::true_type
通过索引找到对应的`name`,然后让`logAndAddImpl`传递给`logAndAdd`(名字会被再`std::forward`给另一个`logAndAddImpl`重载),我们避免了将日志代码放入这个`logAndAddImpl`重载中。
在这个设计中,类型`std::true_type`和`std::false_type`是“标签”tag其唯一目的就是强制重载解析按照我们的想法来执行。注意到我们甚至没有对这些参数进行命名。他们在运行时毫无用处事实上我们希望编译器可以意识到这些标签形参没被使用然后在程序执行时优化掉它们。至少某些时候有些编译器会这样做。通过创建标签对象在`logAndAdd`内部将重载实现函数的调用“分发”给正确的重载。因此这个设计名称为:*tag dispatch*。这是模板元编程的标准构建模块你对现代C++库中的代码了解越多,你就会越多遇到这种设计。
在这个设计中,类型`std::true_type`和`std::false_type`是“标签”tag其唯一目的就是强制重载解析按照我们的想法来执行。注意到我们甚至没有对这些参数进行命名。他们在运行时毫无用处事实上我们希望编译器可以意识到这些标签形参没被使用然后在程序执行时优化掉它们。至少某些时候有些编译器会这样做。通过创建标签对象在`logAndAdd`内部将重载实现函数的调用“分发”*dispatch*给正确的重载。因此这个设计名称为:*tag dispatch*。这是模板元编程的标准构建模块你对现代C++库中的代码了解越多,你就会越多遇到这种设计。
就我们的目的而言,*tag dispatch*的重要之处在于它可以允许我们组合重载和通用引用使用,而没有[Item26](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item26.md)中提到的问题。分发函数——`logAndAdd`——接受一个没有约束的通用引用参数,但是这个函数没有重载。实现函数——`logAndAddImpl`——是重载的,一个接受通用引用参数,但是重载规则不仅依赖通用引用形参,还依赖新引入的标签形参,标签值设计来保证有不超过一个的重载是合适的匹配。结果是标签来决定采用哪个重载函数。通用引用参数可以生成精确匹配的事实在这里并不重要。(译者注:这里确实比较啰嗦,如果理解了上面的内容,这段完全可以没有。)
### 约束使用通用引用的模板
*tag dispatch*的关键是存在单独一个函数没有重载给客户端API。这个单独的函数分发给具体的实现函数。创建一个没有重载的分发函数通常是容易的但是[Item26](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item26.md)中所述第二个问题案例是`Person`类的完美转发构造函数,是个例外。编译器可能会自行生成拷贝和移动构造函数,所以即使你只写了一个构造函数并在其中使用*tag dispatch*,有一些对构造函数的调用也被编译器生成的函数处理,绕过了分机制。
*tag dispatch*的关键是存在单独一个函数没有重载给客户端API。这个单独的函数分发给具体的实现函数。创建一个没有重载的分发函数通常是容易的但是[Item26](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item26.md)中所述第二个问题案例是`Person`类的完美转发构造函数,是个例外。编译器可能会自行生成拷贝和移动构造函数,所以即使你只写了一个构造函数并在其中使用*tag dispatch*,有一些对构造函数的调用也被编译器生成的函数处理,绕过了分机制。
实际上,真正的问题不是编译器生成的函数会绕过*tag diapatch*设计,而是不**总**会绕过去。你希望类的拷贝构造函数总是处理该类型的左值拷贝请求,但是如同[Item26](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item26.md)中所述提供具有通用引用的构造函数会使通用引用构造函数在拷贝non-`const`左值时被调用(而不是拷贝构造函数)。那个条款还说明了当一个基类声明了完美转发构造函数,派生类实现自己的拷贝和移动构造函数时会调用那个完美转发构造函数,尽管正确的行为是调用基类的拷贝或者移动构造。
@ -208,7 +208,7 @@ public:
派生类仅仅是按照常规的规则生成了自己的移动和拷贝构造函数,所以这个问题的解决还要落实在基类,尤其是控制是否使用`Person`通用引用构造函数启用的条件。现在我们意识到不只是禁止`Person`类型启用模板构造函数,而是禁止`Person`**以及任何派生自`Person`**的类型启用模板构造函数。讨厌的继承!
你应该不意外在这里看到标准库中也有*type trait*判断一个类型是否继承自另一个类型,就是`std::is_base_of`。如果`std::is_base_of<T1, T2>`是true就表示`T2`派生自`T1`。类型也可被认为是从他们自己派生,所以`std::is_base_of<T, T>::value`总是`true`。这就很方便了,我们想要修正控制`Person`完美转发构造函数的启用条件,只有当`T`在消除引用和cv限定符之后并且既不是`Person`又不是`Person`的派生类时,才满足条件。所以使用`std::is_base_of`代替`std::is_same`就可以了:
你应该不意外在这里看到标准库中也有*type trait*判断一个类型是否继承自另一个类型,就是`std::is_base_of`。如果`std::is_base_of<T1, T2>`是true就表示`T2`派生自`T1`。类型也可被认为是从他们自己派生,所以`std::is_base_of<T, T>::value`总是true。这就很方便了我们想要修正控制`Person`完美转发构造函数的启用条件,只有当`T`在消除引用和cv限定符之后并且既不是`Person`又不是`Person`的派生类时,才满足条件。所以使用`std::is_base_of`代替`std::is_same`就可以了:
```cpp
class Person {
@ -263,7 +263,7 @@ public:
>
explicit Person(T&& n) //对于std::strings和可转化为
: name(std::forward<T>(n)) //std::strings的实参的构造函数
{ … } //
{ … }
explicit Person(int idx) //对于整型实参的构造函数
: name(nameFromIdx(idx))