枚举类型 vs 强类型枚举

87

我是一个C++编程的初学者。

今天我接触到了一个新的主题:强类型enum。我进行了一些研究,但到目前为止,我仍然无法找出为什么我们需要它以及它的用途是什么?

例如如果我们有:

enum xyz{a, b, c};
/*a = 0, b = 1, c = 2, (Typical C format)*/

为什么我们需要写:

enum class xyz{a, b, c};

我们在这里尝试做什么? 我最重要的疑问是如何使用它。 你能提供一个小例子,让我理解一下吗?

5个回答

122

好的,首先是一个例子:旧版的枚举类型没有自己的作用域:

enum Animals {Bear, Cat, Chicken};
enum Birds {Eagle, Duck, Chicken}; // error! Chicken has already been declared!

enum class Fruits { Apple, Pear, Orange };
enum class Colours { Blue, White, Orange }; // no problem!

其次,它们隐式地转换为整数类型,这可能会导致奇怪的行为:

bool b = Bear && Duck; // what?

最后,你可以指定C++11枚举的底层整型:

enum class Foo : char { A, B, C};

之前,枚举类型的基础类型未指定,在不同平台之间可能会导致兼容性问题。 编辑 在评论中指出,您也可以在C++11中指定“旧样式”枚举的基础整数类型。


我们需要声明/定义enum class Coloursenum class Fruits吗?因为当我在VS 2010中编写代码时,它会在class下面抛出一个错误“期望定义或标记名称”。 - Rasmi Ranjan Nayak
另外,在C++11中,“普通”枚举与C++98中不同,其默认底层类型未定义。 - Konstantin Burlachenko
2
同时,如果指定了底层类型,您可以对枚举进行前向声明。在C++11和C++98中,普通的枚举不允许这样做。Microsoft编译器允许您进行枚举的前向声明,但这只是MS的扩展,它不是标准的(例如gcc不允许)。因此,现在这样的语句是合法的:enum ForwardDeclare: std::uint8_t; - Konstantin Burlachenko
我们可以拥有作用域枚举,同时也可以隐式转换为整数类型吗? - S.S. Anne
2
@S.S.Anne 不,隐式转换会削弱强类型枚举的目的。定义一个模板函数来显式地执行转换,使用std::underlying_type<T>::type来提取类型。 - Dana M

18

这篇IBM页面上有一篇关于枚举(enum)的好文章,详细又写得很好。以下是一些重要的要点:

作用域枚举解决了常规枚举所遇到的大部分限制:完全类型安全、明确定义的底层类型、作用域问题和前向声明。

  • 通过禁止将作用域枚举隐式转换为其他类型来实现类型安全。
  • 您获得了一个新的作用域,并且该枚举不再位于封闭作用域中,从而避免了名称冲突。
  • 作用域枚举使您能够指定枚举的底层类型,默认情况下,如果您选择不指定,则为int类型。
  • 具有固定底层类型的任何枚举都可以进行前向声明。

2
第三点和第四点并不局限于作用域枚举;您可以为任何枚举指定其基础类型。 - Mike Seymour
1
有没有人有一个PDF的链接,可以让里面的代码示例在我的PDF阅读器中正常显示?现在的版本有点问题,看不到具体内容。 - sinback
@SingerOfTheFall 作用域枚举?你是指强类型枚举吗? - John

11

enum class 的值实际上是 enum class 类型的,而不是像 C 中的枚举那样是 underlying_type 类型的。

enum xyz { a, b, c};
enum class xyz_c { d, f, e };

void f(xyz x)
{
}

void f_c(xyz_c x)
{
}

// OK.
f(0);
// OK for C++03 and C++11.
f(a);
// OK with C++11.
f(xyz::a);
// ERROR.
f_c(0);
// OK.
f_c(xyz_c::d);

8
The enum类("新枚举","强枚举")解决了传统C++枚举的三个问题:
1. 传统枚举隐式转换为int,当某人不想让枚举作为整数时会导致错误。
2. 传统枚举将它们的枚举器导出到周围的范围中,引起名称冲突。
3. 不能指定枚举的底层类型,导致混淆、兼容性问题,并使前向声明不可能。
"enum class"("强枚举")是强类型和作用域限定的。
enum Alert { green, yellow, orange, red }; // traditional enum

enum class Color { red, blue };   // scoped and strongly typed enum
                                  // no export of enumerator names into enclosing scope
                                  // no implicit conversion to int
enum class TrafficLight { red, yellow, green };

Alert a = 7;              // error (as ever in C++)
Color c = 7;              // error: no int->Color conversion

int a2 = red;             // ok: Alert->int conversion
int a3 = Alert::red;      // error in C++98; ok in C++11
int a4 = blue;            // error: blue not in scope
int a5 = Color::blue;     // error: not Color->int conversion

Color a6 = Color::blue;   // ok

如图所示,传统的枚举类型按照惯例工作,但现在你可以选择使用枚举类型名称来限定其作用范围。
新的枚举类型是"enum class",因为它们结合了传统的枚举类型(命名值)和类的特点(有范围的成员和无转换)。
能够指定底层类型使得枚举类型之间的互操作更加简单,并保证了枚举类型的大小。
enum class Color : char { red, blue };  // compact representation

enum class TrafficLight { red, yellow, green };  // by default, the underlying type is int

enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };   // how big is an E?
                                                 // (whatever the old rules say;
                                                 // i.e. "implementation defined")

enum EE : unsigned long { EE1 = 1, EE2 = 2, EEbig = 0xFFFFFFF0U };   // now we can be specific

它还可以实现枚举的前向声明:
enum class Color_code : char;     // (forward) declaration
void foobar(Color_code* p);       // use of forward declaration
// ...
enum class Color_code : char { red, yellow, green, blue }; // definition

底层类型必须是有符号或无符号整数类型之一;默认为 int

在标准库中,enum 类型用于:

  1. 映射系统特定错误代码:在 <system_error> 中: enum class errc;
  2. 指针安全指示器:在 <memory> 中: enum class pointer_safety { relaxed, preferred, strict };
  3. I/O 流错误:在 <iosfwd> 中: enum class io_errc { stream = 1 };
  4. 异步通信错误处理:在 <future> 中: enum class future_errc { broken_promise, future_already_retrieved, promise_already_satisfied };

其中几个具有运算符,例如定义了 == 运算符。


3

枚举范围

枚举将它们的枚举值导出到周围的范围。这有两个缺点。首先,如果在同一作用域中声明的两个不同枚举中有相同名称的枚举值,则可能导致名称冲突; 其次,无法使用包括枚举名称在内的完全限定名称来使用枚举值。

enum ESet {a0, a, a1, b1, c3};
enum EAlpha{a, b, c}

select = ESet::a; // error
select = a;       // is ambigious

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接