Update Item 10

This commit is contained in:
racaljk 2018-05-06 12:10:24 +08:00
parent b46f5b53ea
commit 2de91cccc1
2 changed files with 58 additions and 76 deletions

View File

@ -8,7 +8,7 @@ enum Color { black, white, red }; // black, white, red 和
auto white = false; // 错误! white早已在这个作用
// 域中存在
```
事实上这些枚举名泄漏进和它们所被定义的enum域一样的作用域有一个官方的术语未限域(unscoped)枚举。在C++11中它们有一个相似物限域枚举它不会导致枚举名泄漏
事实上这些枚举名泄漏进和它们所被定义的enum域一样的作用域有一个官方的术语:未限域枚举(unscoped enum)在C++11中它们有一个相似物限域枚举(scoped enum),它不会导致枚举名泄漏:
```cpp
enum class Color { black, white, red }; // black, white, red
// 限制在Color域内
@ -21,7 +21,7 @@ auto c = Color::white; // 也没问题也符合条款5的建
```
因为限域枚举是通过**enum class**声明,所以它们有时候也被称为枚举类(enum classes)。
使用限域枚举减少命名空间污染是一个足够合理使用它而不是它的同胞未限域枚举的理由,其实限域枚举还有第二个吸引人的优点:在它的作用域中枚举名是强类型。未限域枚举中的枚举名会隐式转换为整型(现在,也可以转换为浮点类型)。因此下面这种歪曲语义的做法也是完全有效的:
使用限域枚举减少命名空间污染是一个足够合理使用它而不是它的同胞未限域枚举的理由,其实限域枚举还有第二个吸引人的优点:在它的作用域中,枚举名是强类型。未限域枚举中的枚举名会隐式转换为整型(现在,也可以转换为浮点类型)。因此下面这种歪曲语义的做法也是完全有效的:
```cpp
enum Color { black, white, red }; // 未限域枚举
std::vector<std::size_t> // func返回x的质因子
@ -35,7 +35,7 @@ if (c < 14.5) { // Color与double比较 (!)
}
```
在**enum**后面写一个**class**就可以将非限域枚举转换为限域枚举,接下来就是完全不同的故事展开了。
现在不存在任何隐式的转换可以将限域枚举中的枚举量转化为任何其他类型。
现在不存在任何隐式转换可以将限域枚举中的枚举名转化为任何其他类型。
```cpp
enum class Color { black, white, red }; // Color现在是限域枚举
Color c = Color::red; // 和之前一样,只是
@ -56,17 +56,17 @@ if (static_cast<double>(c) < 14.5) { // 奇怪的代码,但是
}
```
看来比起非限域枚举而言限域枚举有第三个好处,因为限域枚举可以前声明。比如,它们可以不指定枚举量直接前向声明:
似乎比起非限域枚举而言限域枚举有第三个好处,因为限域枚举可以前置声明。比如,它们可以不指定枚举名直接前向声明:
```cpp
enum Color; // 错误!
enum class Color; // 没问题
```
这是一个误导。在C++11中非限域枚举也可以被前声明,但是只有在做一些其他工作后才能实现。这些工作来源于一个事实:
在C++中所有的枚举都有一个由编译器决定的基础整型类型。对于非限域枚举比如Color
其实这是一个误导。在C++11中非限域枚举也可以被前声明,但是只有在做一些其他工作后才能实现。这些工作来源于一个事实:
在C++中所有的枚举都有一个由编译器决定的整型的基础类型。对于非限域枚举比如`Color`
```cpp
enum Color { black, white, red };
```
编译器可能选择**char**作为基础类型,因为这里只需要表示三个值。然而,有些枚举中的枚举值范围可能会大些,比如:
编译器可能选择`char`作为基础类型,因为这里只需要表示三个值。然而,有些枚举中的枚举值范围可能会大些,比如:
```cpp
enum Status { good = 0,
failed = 1,
@ -79,9 +79,9 @@ enum Status { good = 0,
为了高效使用内存,编译器通常在确保能包含所有枚举值的前提下为枚举选择一个最小的基础类型。在一些情况下,编译器
将会优化速度舍弃大小这种情况下它可能不会选择最小的基础类型而是选择对优化大小有帮助的类型。为此C++98
只支持枚举定义(所有枚举全部列出来);枚举声明是不被允许的。这使得编译器能为之前使用的每一个枚举选择一个基础类型。
只支持枚举定义(所有枚举全部列出来);枚举声明是不被允许的。这使得编译器能为之前使用的每一个枚举选择一个基础类型。
但是不能前声明枚举也是有缺点的。最大的缺点莫过于它可能增加编译依赖。再次考虑**Status**枚举:
但是不能前声明枚举也是有缺点的。最大的缺点莫过于它可能增加编译依赖。再次考虑**Status**枚举:
```cpp
enum Status { good = 0,
failed = 1,
@ -90,7 +90,7 @@ enum Status { good = 0,
indeterminate = 0xFFFFFFFF
};
```
这种*enum*很有可能用于整个系统,因此系统中每个包含这个头文件的组件都会依赖它。如果引入一个新状态值,
这种**enum**很有可能用于整个系统,因此系统中每个包含这个头文件的组件都会依赖它。如果引入一个新状态值,
```cpp
enum Status { good = 0,
failed = 1,
@ -100,15 +100,15 @@ enum Status { good = 0,
indeterminate = 0xFFFFFFFF
};
```
那么可能整个系统都得重新编译,即使只有一个子系统——或者一个函数使用了新添加的枚举。这是大家都不希望看到的。C++11中的前置声明可以解决这个问题。
那么可能整个系统都得重新编译,即使只有一个子系统——或者一个函数使用了新添加的枚举。这是大家都不希望看到的。C++11中的前置声明可以解决这个问题。
比如这里有一个完全有效的限域枚举声明和一个以该限域枚举作为形参的函数声明:
```cpp
enum class Status; // forward declaration
void continueProcessing(Status s); // use of fwd-declared enum
```
即使*Status*的定义发生改变,包含这些声明的头文件也不会重新编译。而且如果*Status*添加一个枚举量比如添加一个_audited_*continueProcessing*的行为不受影响(因为*continueProcessing*没有使用这个新添加的_audited_*continueProcessing*也不需要重新编译。
但是如果编译器在使用它前需要知晓该枚举的大小该怎么声明才能让C++11做到C++98不能做到的事情呢
答案很简单:限域枚举的基础类型总是已知的,对于非限域枚举,你可以指定它。默认情况下,限域枚举的基础类型是*int*
即使**Status**的定义发生改变,包含这些声明的头文件也不会重新编译。而且如果**Status**添加一个枚举名比如添加一个_audited_**continueProcessing**的行为不受影响(因为**continueProcessing**没有使用这个新添加的_audited_**continueProcessing**也不需要重新编译。
但是如果编译器在使用它前需要知晓该枚举的大小该怎么声明才能让C++11做到C++98不能做到的事情呢
答案很简单:限域枚举的基础类型总是已知的,对于非限域枚举,你可以指定它。默认情况下,限域枚举的基础类型是`int`
```cpp
enum class Status; // 基础类型是int
```
@ -118,7 +118,7 @@ enum class Status: std::uint32_t;   // Status的基础类型
                                   // 是std::uint32_t
                                   // (需要包含 <cstdint>)
```
不管怎样,编译器都知道限域枚举中的枚举占用多少字节。要为非限域枚举指定基础类型,你可以同上,然后前向声明一下:
不管怎样,编译器都知道限域枚举中的枚举占用多少字节。要为非限域枚举指定基础类型,你可以同上,然后前向声明一下:
```cpp
enum Color: std::uint8_t;   // 为非限域枚举Color指定
                           // 基础为
@ -134,37 +134,30 @@ enum class Status: std::uint32_t { good = 0,
indeterminate = 0xFFFFFFFF
};
```
由于限域枚举避免命名空间污染而且不接受隐式类型转换,你可能会很惊讶听到至少有一种情况下非限域枚举是很有用的。
那就是引用C++11 tuples中的字段的时候。比如在社交网站中假设我们有一个tuple保存了用户的名字email地址声望点
website:
限域枚举避免命名空间污染而且不接受隐式类型转换,但它并非万事皆宜,你可能会很惊讶听到至少有一种情况下非限域枚举是很有用的。
那就是获取C++11 tuples中的字段的时候。比如在社交网站中假设我们有一个`tuple`保存了用户的名字email地址声望点
```cpp
using UserInfo = // type alias; see Item 9
std::tuple<std::string, // name
std::string, // email
std::size_t> ; // reputation
using UserInfo = // 类型别名参见Item 9
std::tuple<std::string, // 名字
std::string, // email地址
std::size_t> ; // 声望
```
Though the comments indicate what each field of the tuple represents, thats probably
not very helpful when you encounter code like this in a separate source file:
虽然注释说明了tuple各个字段对应的意思但当你在另文件遇到下面的代码那之前的注释就不是那么有用了
```cpp
UserInfo uInfo; // object of tuple type
UserInfo uInfo; // tuple对象
auto val = std::get<1>(uInfo); // 获取第一个字段
```
auto val = std::get<1>(uInfo); // get value of field 1
As a programmer, you have a lot of stuff to keep track of. Should you really be
expected to remember that field 1 corresponds to the users email address? I think
not. Using an unscoped enum to associate names with field numbers avoids the need
to:
作为一个程序员你有很多工作要持续跟进。你应该记住第一个字段代表用户的email地址吗我认为不。
可以使用非限域枚举将名字和字段编号关联起来以避免上述需求:
```cpp
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
UserInfo uInfo;
auto val = std::get<uiEmail>(uInfo); // ah, get value of
// email field
auto val = std::get<uiEmail>(uInfo); // 获取用户email
```
What makes this work is the implicit conversion from UserInfoFields to
std::size_t, which is the type that std::get requires.
The corresponding code with scoped enums is substantially more verbose:
之所以它能正常工作是因为`UserInfoFields`中的枚举名隐式转换成**std::size_t**了,其中**std::size_t**是**std::get**模板实参所需的。
对应的限域枚举版本就很啰嗦了:
```cpp
enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
@ -173,67 +166,56 @@ auto val =
std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>
(uInfo);
```
The verbosity can be reduced by writing a function that takes an enumerator and
returns its corresponding std::size_t value, but its a bit tricky. std::get is a template,
and the value you provide is a template argument (notice the use of angle
brackets, not parentheses), so the function that transforms an enumerator into a
std::size_t has to produce its result during compilation. As Item 15 explains, that
means it must be a constexpr function.
In fact, it should really be a constexpr function template, because it should work
with any kind of enum. And if were going to make that generalization, we should
generalize the return type, too. Rather than returning std::size_t, well return the
enums underlying type. Its available via the std::underlying_type type trait. (See
Item 9 for information on type traits.) Finally, well declare it noexcept (see Item 14),
because we know it will never yield an exception. The result is a function template
toUType that takes an arbitrary enumerator and can return its value as a compiletime
constant:
为避免这种冗长的表示,我们可以写一个函数传入枚举名并返回对应的**std::size_t**值,但这有一点技巧性。
**std::get**是一个模板(函数),需要你给出一个**std::size_t**值的模板实参(注意使用`<>`而不是`()`),因此将枚举名变换为**std::size_t**值会发生在编译期。
如Item 15提到的那必须是一个**constexpr**模板函数。
事实上,它也的确该是一个**constexpr**函数,因为它应该能用于任何`enum`。
如果我们想让它更一般化,我们还要泛化它的返回类型。较之于返回**std::size_t**,我们更应该泛化枚举的基础类型。
这可以通过**std::underlying_type**这个`type trait`获得。参见Item 9关于type trait的内容
最终我们还要再加上**noexcept**修饰参见Item 14因为我们知道它肯定不会产生异常。
根据上述分析最终得到的**toUType**模板函数在编译期接受任意枚举名并返回它的值:
```cpp
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumerator) noexcept
toUType(E enumerator) noexcept
{
return
static_cast<typename
std::underlying_type<E>::type>(enumerator);
return
static_cast<typename
std::underlying_type<E>::type>(enumerator);
}
```
In C++14, toUType can be simplified by replacing typename std::underly
ing_type<E>::type with the sleeker std::underlying_type_t (see Item 9):
在C++14中**toUType**还可以进一步用`std::underlying_type_t`参见Item 9代替`typename std::underly
ing_type<E>::type`打磨:
```cpp
template<typename E> // C++14
constexpr std::underlying_type_t<E>
toUType(E enumerator) noexcept
toUType(E enumerator) noexcept
{
return static_cast<std::underlying_type_t<E>>(enumerator);
return static_cast<std::underlying_type_t<E>>(enumerator);
}
```
The even-sleeker auto return type (see Item 3) is also valid in C++14:
还可以再用C++14 auto参见Item 3打磨一下代码
```cpp
template<typename E> // C++14
constexpr auto
toUType(E enumerator) noexcept
toUType(E enumerator) noexcept
{
return static_cast<std::underlying_type_t<E>>(enumerator);
return static_cast<std::underlying_type_t<E>>(enumerator);
}
```
Regardless of how its written, toUType permits us to access a field of the tuple like
this:
不管它怎么写,**toUType**现在允许这样访问tuple的字段了
```cpp
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
```
Its still more to write than use of the unscoped enum, but it also avoids namespace
pollution and inadvertent conversions involving enumerators. In many cases, you
比起使用非限域枚举,限域有很多可圈可点的地方,它避免命名空间污染,防止不经意间使用隐式转换。
(下面这句我没看懂,保留原文。。(是什么典故吗。。。))
In many cases, you
may decide that typing a few extra characters is a reasonable price to pay for the ability
to avoid the pitfalls of an enum technology that dates to a time when the state of
the art in digital telecommunications was the 2400-baud modem.
记住
• C++98-style enums are now known as unscoped enums.
• Enumerators of scoped enums are visible only within the enum. They convert
to other types only with a cast.
• Both scoped and unscoped enums support specification of the underlying type.
The default underlying type for scoped enums is int. Unscoped enums have no
default underlying type.
• Scoped enums may always be forward-declared. Unscoped enums may be
forward-declared only if their declaration specifies an underlying type.
+ C++98的枚举即非限域枚举
+ 限域枚举的枚举名仅在enum内可见。要转换为其它类型只能使用cast。
+ 非限域/限域枚举都支持基础类型说明语法,限域枚举基础类型默认是`int`。非限域枚举没有默认基础类型。
+ 限域枚举总是可以前置声明。非限域枚举仅当指定它们的基础类型时才能前置。

View File

@ -20,7 +20,7 @@
1. [Item 7:区别使用()和{}创建对象](https://github.com/racaljk/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item7.md)
2. [Item 8:优先考虑nullptr而非0和NULL](https://github.com/racaljk/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item8.md)
3. [Item 9:优先考虑别名声明而非typedefs](https://github.com/racaljk/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item9.md)
4. [Item 10:优先考虑限域枚举而非未限域枚举](https://github.com/racaljk/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item10.md) _updating_
4. [Item 10:优先考虑限域枚举而非未限域枚举](https://github.com/racaljk/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item10.md) _revised_
5. Item 11:优先考虑使用delete而非使用未定义的私有声明
6. Item 12:使用override声明重载函数
7. Item 13:优先考虑const_iterator而非iterator