• 【C++】enum class 域化枚举


    C++11 中,枚举的关键字为 enum class,即在 enum 后加 class,与 C++98 的 “plain” enum 区别如下:

    enum class Color { red, green, blue };   
    enum Color { red, green, blue };  
    
    • 1
    • 2

    1 enum class 的优点

    1.1 防止命名空间污染

    C++98 的 enum 是 “非域化枚举”(unscoped enums)

    // yellow, green, blue are in same scope as Color 
    enum Color { yellow, green, blue};  
    
    // error! yellow already declared in this scope 
    auto yellow = false; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    enum class 将 { } 内的变量,加上 class 限制其在 { } 作用域内可见,是"域化枚举" (scoped enums),可以防止命名空间污染

    // yellow, green, blue are scoped to Color
    enum class Color { yellow, green, blue};
     
    // fine, no other "yellow" in scope
    auto yellow = false;
     
    // also fine  
    auto c = Color::yellow;  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.2 强类型枚举

    非域化的枚举成员,可隐式的转换为广义整型 (integral types)

    enum Color { yellow, green, blue};
     
    // func. returning prime factors of x
    std::vector<std::size_t> primeFactors(std::size_t x);
     
    Color c = blue;
     
    if (c < 14.5) // compare Color to double (!)
    {
        auto factors = primeFactors(c); // compute prime factors of a Color (!)
        ... ...
    }     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    而域化的枚举成员,不能隐式的转换为广义整型

    enum class Color { yellow, green, blue };
     
    Color c = Color::blue;
     
    // error! can't compare Color and double
    if (c < 14.5) 
    {
        // suspect, but it compiles
        auto factors = primeFactors(c); // error! can't pass Color to function expecting std::size_t
        ... ...
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    正确的方式是用 C++ 的类型转换符,如 static_cast<>()

    // odd code, but it's valid
    if (static_cast<double>(c) < 14.5)     
    {
        auto factors = primeFactors(static_cast<std::size_t>(c)); 
        ... ...
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.3 前置声明

    enum class 支持前置声明,即不用初始化枚举成员,声明一个枚举类型

    enum class Color;  
    
    • 1
    1. 新增枚举成员
      enum 在声明时,编译器会选择占用内存最小的一种潜在类型 (underlying types),来代表每一个枚举成员
    // compiler may choose char type
    enum Color { black, white, red };   
    
    • 1
    • 2

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

    enum Status {
        good = 0,
        failed = 1,
        incomplete = 100,
        corrupt = 200,
        indeterminate = 0xFFFFFFFF
    };  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    enum Status {
        good = 0,
        failed = 1,
        incomplete = 100,
        corrupt = 200,
        audited = 500,
        indeterminate = 0xFFFFFFFF
    };  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    // forward declaration
    enum class Status;
     
    // use of fwd-declared enum 
    void continueProcessing(Status s); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 潜在类型
      enum class 的潜在类型 (underlying type),缺省为 int 型,当然也可自定义潜在类型。无论哪种方式,编译器都会预先知道枚举成员的大小
    // underlying type is int
    enum class Status;
     
    // underlying type for Status is std::uint32_t (from <cstdint>)
    enum class Status: std::uint32_t;
     
    // specify underlying type on enum's definition
    enum class Status: std::uint32_t 
    {
        good = 0,
        failed = 1,
        incomplete = 100,
        corrupt = 200,
        audited = 500,
        indeterminate = 0xFFFFFFFF
    };    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2 enum 的优点

    一般情况下,优先用 enum class,但在某些特定情况下(如 std::tuple),enum 反而有一定的优势

    2.1 非域化枚举

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

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

    当如下代码,在另一个源文件里时,很有可能已经忘记了 tuple 中第一个成员的含义,是名字还是邮箱?

    // object of tuple type
    UserInfo uInfo; 
     ...
    
    // get value of field 1 
    auto val = std::get<1>(uInfo);  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是,用 enum class,则不必担心忘记 tuple 中的顺序

    enum UserInfoFields { uiName, uiEmail, uiReputation };
    UserInfo uInfo;
    ... ...
     
    // get value of email field 
    auto val = std::get<uiEmail>(uInfo);   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2 域化枚举

    上例中,假如用 enum class,则会涉及类型转换,看上去比较繁琐

    enum class UserInfoFields { uiName, uiEmail, uiReputation };
     
    UserInfo uInfo; // as beforeauto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时,可用模板函数,将枚举成员 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);
    }   
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样,稍微缩减了代码的复杂度,但相比于 enum,还是有些繁琐

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

    小结

    1. C++98 的 enum 是“非域化的”;而 C++11 的 enum class 是“域化的”,限制了枚举成员只在域内可见

    2. enum class 的缺省潜在类型 (underlying type) 是 int 型,而 enum 没有缺省潜在类型

    3. enum class 一般总是前置声明,而 enum 只有在指定了潜在类型时才可以是前置声明- 1.enum class是类型安全的

    https://zhuanlan.zhihu.com/p/501309032

    • 1.enum class是类型安全的

    • 2.枚举定义将被限制在枚举作用域内,不能隐式转换为整数类型,但是可以强制转化为整数类型

    • 3.使用enum class必须带作用域名。

    在这里插入图片描述
    https://blog.csdn.net/qq_38409301/article/details/121180318
    https://blog.csdn.net/qq_43331089/article/details/121655801

  • 相关阅读:
    MySQL什么情况下会导致索引失效?
    源码分析:Transport 开发
    Jenkins pipeline中的全局变量
    Maven
    android junit 单元测试与输出日志信息查看处理
    CMT2380F32模块开发7-reset例程
    关于Flask模板_过滤器的介绍和使用
    中科大给师生们发了一封钓鱼邮件 结果3000多人上当了
    JavaSE => 继承和组合
    Docker:容器网络互联
  • 原文地址:https://blog.csdn.net/weixin_41838721/article/details/125403861