Item 10: Prefer scoped enums to unscoped enums(C++98和C++11枚举)

Things to Remember:

  • C++98风格的enums被认为是unscoped enums
  • scped enums的枚举值仅enum可见,如果想要转换为其他类型需要使用cast
  • scoped 和 unscoped enums都支持底层规范类型。scoped enum默认的底层规范类型是int。unscoped enums没有默认的underlying type
  • Scoped enums可能总是forward-declared。Unscped enums只有当指定了底层类型的时候才可以forwar-declared。

    一般来说,在大括号内声明名称,名称的可见性会被限制在大括号作用域内。但是这不包括C++98风格的枚举。枚举的每一个名称都属于enum作用域,这意味着在作用域内的每一个枚举名称都不能相同。

   在下面这个例子中,black等和Color属于同一个作用域,这些枚举名称泄露到包含枚举的作用域中,专业名词叫:unscoped。

enum Color{black,white,red};//in same scope as Color

auto white = false;//error! white already declared in this scope

    C++11的新版本作用域枚举则不会以这种形式被泄露出作用域。因为作用于枚举是用“enum class”声明的,有时候也叫枚举类。

enum class Color{black, white,red};
auto white = false;//fine, no other
Color c = white;//error!no enumerator named "white" in this scope
Color c = Color::white;//fine

   但是,在"enum"之前加上"class"没有隐式转换从enum作用域枚举值到其他的类型。如果一定想要将Color转换为其他类型,那么就使用cast。

Color c = Color::red;
if(c < 14.5)//error!can't compare Color and double
if(static_cast<double>(c)<14.5)//odd code,but it's valid

 

    scoped enums相比unscoped enums看上去有3个好处,因为scoped enums可以向前声明,例如,可以只声明names而不声明其枚举值。哈~但是这是一种误导。在C++11中,unscoped enums也可以向前声明(forward-declared),但是仅仅是在做了一系列的工作之后。每一个枚举在C++中都有一个由编译器(underlying type)决定的完整的底层类型。

  

enum Color;//error!
enum class Color;//fine

enum Color{black,white,red};
enum Status{
     good = 0,
     failed = 1,
     incomplete = 100,
     corrupt = 200,
    indeterminate = 0xFFFFFFF
};

     

    编译器可能会为Color选择char作为underlying type,因为此时仅仅有3个数值需要被代表。然而Status则有一定范围内更大的值,这里需要被代表的是从0到0xFFFFFFF。除了一些不寻常的机器(char至少32bits),编译器不得不选择一个大于char的整数类型来表示Status的值。

    为了更有效的使用memory,编译器通常想要为枚举enum选择最小的基础类型,以足以表示其枚举值的范围。在某些类型,编译器将会选择优化速度而不是优化大小,编译器可能不会选择最小的underlying 类型,但是编译器当然是想要去优化大小的。为了实现这一点,C++98只支持enum定义(列举出所有枚举值的方式);enum声明是不允许的。这让编译器能够在使用枚举值之前为每一个枚举值的underlying type选择类型。但是既然编译器在使用enum之前需要知道enum的类型大小,那么C++11的enums怎么能在使用向前声明的情况下知道呢?答案和简单:underlying type对于scoped enum总是知道的,对于unscoped enums,你可以指定它。

enum class Status;//underlying type is int
enum class Status:std::uint32_t;//underlying type for Status is std::uint32_t

enum Color:std::uint8_t;//fwd decl for unscoped enum;
enum class Status:std::uint32_t{
    good = 0,
    failed = 1,
    ...
}

 

    但是不支持forward-declare enums也有缺点。最值得注意的是编译的依赖关系增加。例如上面的Status enum,假设这个enum是一个很有可能在整个系统中使用的枚举,包含在一个系统中每一个part都依赖的头文件中。如果一个新的枚举值被添加,那么整个系统都将不得不重新编译。这是程序员hate的!!!但是在C++11中的forward-declare enum所能够避免的。

    按照下述例子声明,那么包含这些定义的头文件不需要被重新编译。此外,如果状态被修改,但是continueProcessing的行为是不受影响的(因为continueProcessing没有使用到新添加的这个枚举值),continueProcessing的实现也是不需要重新编译了。

enum class Status; //forward declaration
void continueProcessing(Status s);//use of fwd-declared enum

 

    考虑到scoped enums避免了namespace污染和不会受到隐式转换的影响,诸多好处,但是还有有一种情况是unscoped enums有用的:当引用C++11的std::tuples的时候。

下面这个例子,假设我们有一个元组,包含了name,email address,用户在社交网站上的reputation value。实现auto val = std::get<uiName>(uInfo)的原因是从UserInfoFields到std::size_t的隐式转换。

//unscoped enum
using UserInfo 
    std::tuple<std::string,//name
    std::string,//email
    std::size_t>;//reputation
enum UserInfoFields{uiName, uiEmail, uiReputation};    
UserInfo uInfo;//object of tuple type
auto val = std::get<uiName>(uInfo);//get value of field 1  

//scoped enum
enum class UserInfoFields{uiName, uiEmail, uiReputation};
UserInfo uInfo;
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiName)>(uInfo);  

 

    我们当然也可以通过编写一个带有枚举器并且返回对应的std::size_t值的函数来减少scoped enum上述代码的复杂程度。std::get是一个模板,我们需要提供的值是一个模板参数,(注意当前是使用的尖括号不是圆括号),所以将枚举值转换成为一个std::size_t意味着必须是在编译期间生成结果,这就意味着他必须是一个constexpr函数(Item 15)。并且,为了扩宽使用条件,我们应该返回的是一个std::underlying_type而不是一个std::size_t。最后,此函数将会声明成为一个noexcept,因为我们知道它永远不会产生异常。此函数如下:这个模板函数toUType可以接受任意的枚举值,并且把它作为编译时常量返回。

//C++11
template<typename E>
constexpr typename std::underlying_type<E>::type
    toUType(E enumerator) noexcept
{
    return 
        static_cast<typename
            std::underlying_type<E>::type>(enumerator);
}

//C++14
template<typename E>
constexpr typename std::underlying_type_t<E>//or auto
    toUType(E enumerator) noexcept
{
    return 
        static_cast<typename
            std::underlying_type_t<E>>(enumerator);
}

auto val = std::get<toUType(UserInfoFields::uiName)>(uInfo);

 

    虽然最后scoped enum仍然比unscoped enum代码复杂了一些,但是同时也避免了命名空间污染和无意的类型转换。在很多情况下,可以认为多输入几个字符从而避免enum的缺陷是值得的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值