• C++11 之 scoped enum


      C++11 的枚举是“域化的” (scoped enum),相比 C++98 枚举的“非域化” (unscoped enum),具有如下优点:

     1  命名空间污染 

       声明在 {} 内的变量,其可见性限制在 {} 作用域内,但是非域化枚举 (unscoped enum) 却是例外

    enum Color { yellow, green, blue}; // yellow, green, blue are in same scope as Color
    
    auto yellow = false; // error! yellow already declared in this scope
    
    Color  c = yellow; // fine

      C++11 枚举的关键字为 enum class,可视为一个 "class",能防止“命名空间污染”

    enum class Color { yellow, green, blue}; // yellow, green, blue are scoped to Color
    
    auto yellow = false; // fine, no other "yellow" in scope
    
    Color c = yellow; // error! no enumerator named "yellow" is in this scope
    
    Color c = Color::yellow; // fine

    auto c = Color::yellow; // also fine

    2  强类型枚举

      非域化的枚举成员,可以隐式的转换为广义整型 (integral types),如下所示:

    enum Color { yellow, green, blue}; // unscoped enum
    std::vector<std::size_t> primeFactors(std::size_t x); // func. returning prime factors of x
    
    Color c = blue;
    
    if (c < 14.5) // compare Color to double (!)
    { 
        auto factors = primeFactors(c); // compute prime factors of a Color (!)
        …
    }

      域化的枚举成员,却不可以隐式的转换为广义整型

    enum class Color { yellow, green, blue }; // enum is now scoped
    
    Color c = Color::blue; // as before, but with scope qualifier
    
    if (c < 14.5)  // error! can't compare Color and double
    {
        auto factors = primeFactors(c); // error! can't pass Color to function expecting std::size_t 
        …
    }

      正确的方式是使用 C++ 的类型转换符 (cast)

    if (static_cast<double>(c) < 14.5)     // odd code, but it's valid
    { 
        auto factors = primeFactors(static_cast<std::size_t>(c)); // suspect, but it compiles
        …
    }

    3  前置声明

       域化枚举支持前置声明 (forward-declared),也即可以不用初始化枚举成员而声明一个枚举类型

    enum class Color;

    3.1 新增枚举成员

      非域化枚举(unscoped enum) 在声明时,编译器会选择占用内存最小的一种潜在类型 (underlying types),来代表每一个枚举成员

    enum Color { black, white, red };  // compiler may choose char type

      下面的例子中,编译器可能会选择更大的能够包含 0 ~ 0xFFFFFFFF 范围的潜在类型

    enum Status { 
        good = 0,
        failed = 1,
        incomplete = 100,
        corrupt = 200,
        indeterminate = 0xFFFFFFFF
    };

      非前置声明的缺点在于,当新增一个枚举成员时 (如下例的 audited ),整个系统将会被重新编译一遍,即使只有一个很简单的函数使用了新的枚举成员 (audited)

    enum Status { 
        good = 0,
        failed = 1,
        incomplete = 100,
        corrupt = 200,
        audited = 500,
        indeterminate = 0xFFFFFFFF
    };

      而使用前置声明,当新增枚举成员时,包含这些声明的头文件并不需要重新编译,源文件则根据新枚举成员的使用情况来决定是否重新编译

      如下例,Status 中新增枚举成员 audited,如果函数 continuteProcesing 没有使用 audited,则函数 continuteProcesing 的实现并不需要重新编译

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

    3.2 潜在类型

      域化枚举的潜在类型 (underlying type),缺省为 int 型,当然也可以自行定义潜在类型。不管哪种方式,编译器都会预先知道枚举成员的大小

    enum class Status; // underlying type is int
    
    enum class Status: std::uint32_t; // underlying type for Status is std::uint32_t (from <cstdint>)
    
    enum class Status: std::uint32_t  // specify underlying type on enum's definition
    { good
    = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF };

    4  std::tuple

      一般而言,使用 C++11 域化枚举 (scoped enum) 是比较好的选择,但 C++98 非域化枚举 (unscoped enum) 也并非一无是处

    4.1  非域化枚举

      当涉及到 std::tuple 时,使用 C++98 非域化枚举反而会有优势,如下例所示

      假定一个社交网站中,每一位用户,都使用一种模板类型 - 元组 (tuple) 来包含名字、邮箱、声望值 (name, email, reputation value)

    using UserInfo = std::tuple<std::string, std::string, std::size_t> ; // type alias

      当以下代码在另一个不同的源文件里时 (source file),很有可能忘了元组 (tuple) 的第一个成员到底是名字还是邮箱 (name or email)

    UserInfo uInfo; // object of tuple type
     ...
    
    auto val = std::get<1>(uInfo); // get value of field 1

      但是,使用非域化枚举 (unscoped enum),可以不用担心忘记了元组内的成员顺序

    enum UserInfoFields { uiName, uiEmail, uiReputation };
    UserInfo uInfo; // as before
    …
    auto val = std::get<uiEmail>(uInfo); // get value of email field

    4.2  域化枚举 

      上例中,假如使用域化枚举 (scoped enum),则会用到类型转换,看起来比较繁琐

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

      可以使用一个模板函数,将枚举成员 UserInfoFields::uiEmail 和 std::size_t 类型联系起来

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

     这样,便可以稍微缩减了代码的复杂度。但是相比于非域化枚举,看起来还是有些繁琐

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

    小结:

    1)  C++98 枚举类型是“非域化的”;而C++11 枚举类型是“域化的”,枚举成员只在域内可见

    2)  域化枚举的缺省潜在类型 (underlying type) 是 int 型,而非域化枚举没有缺省潜在类型

    3)  域化枚举一般总是前置声明,而非域化枚举只有在指定了潜在类型时才可以是前置声明

    参考资料:

      <Effective Modern C++> Item 10

  • 相关阅读:
    第二周学习小结
    第一周小结(^_^)
    VS2010和搜狗快捷键冲突
    解除SQL远程编辑表
    SQLServer2005mssqlserver服务与sqlexpress服务有什么区别
    OEA界面生成学习1 总体浏览
    WPF学习:绑定
    OutLook自动存档
    文件目录学习
    AQTime
  • 原文地址:https://www.cnblogs.com/xinxue/p/5425166.html
Copyright © 2020-2023  润新知