C++ enum class枚举的用法(非常详细)
枚举是 C++ 中的一种基本类型,它定义了一个值的集合,通常是一个整数基础类型,它们的命名值是常量,称为枚举器。
使用 enum 关键字声明的枚举称为无作用域枚举(unscoped enumerations),使用 enum class 或者 enum struct 声明的枚举称为作用域枚举(scoped enumeration)。后者是在 C++11 引入的,旨在解决无作用域枚举解决不了的事情,本节将介绍作用域枚举。
使用 enum class 或者 enum struct 声明作用域枚举:
因为作用域枚举是受限制的命名空间,所以 C++20 标准允许我们将它们与 using 指令结合使用。你可以执行以下操作:
1) 使用 using 指令在局部作用域内引入作用域枚举标识符,如下所示:
2) 使用 using enum 指令在局部作用域内引入作用域枚举的所有标识符,如下所示:
3) 在 switch 语句中,使用 using enum 指令引入作用域枚举标识符可以简化代码:
1) 它们把枚举器导出到周围的作用域(基于此,它们被称为无作用域枚举),所以就有以下两个缺陷:
2) 在 C++11 之前,它们没法指定基础类型,必须是整型。类型不能大于 int,除非枚举器值不适应有符号或无符号整数。
基于此,不能前置枚举声明。原因是枚举的大小是未知的。这是因为在定义枚举器的值之前基础类型是未知的,这样编译器不知道选择哪些合适的整数类型。但是这个问题在 C++11 中得到了修复。
3) 枚举器的值会隐式转换为int。这意味着你可以有意或者无意地将有特定含义的枚举和整数混用(这甚至与枚举的含义无关),而且编译器不会发出警告。
作用域枚举基本上是强类型枚举,其行为与无作用域枚举不同:
1) 它们不会把枚举器导出到周围的作用域。之前提到的两个枚举会变成如下这样,它们不再产生产生命名冲突并且可以使用完全限定名称的枚举器:
2) 可以指定基础类型。除了作用域枚举可以指定基础类型之外,无作用域枚举中基础类型的规则同样适用于作用域枚举。这也解决了前置声明的问题,因为在定义可用之前就已经知道基础类型了:
3) 在作用域枚举中,枚举器的值不再隐式地转换为 int。除非指定了显式转换,否则将 enum class 的值赋给整数变量会导致编译错误:
然而,作用域枚举也有缺陷:它们是受限制的命名空间。它们不会把它们的标识符导出到外面的作用域,这一点有时是不方便的。例如,如果你写了一个 switch 语句,就必须在每个 case 语句后面重复使用枚举名,下面的例子说明了这一点:
使用 enum 关键字声明的枚举称为无作用域枚举(unscoped enumerations),使用 enum class 或者 enum struct 声明的枚举称为作用域枚举(scoped enumeration)。后者是在 C++11 引入的,旨在解决无作用域枚举解决不了的事情,本节将介绍作用域枚举。
C++ enum class的使用
当在 C++ 程序中使用枚举时,应该优先使用作用域枚举,而不是无作用域枚举。使用 enum class 或者 enum struct 声明作用域枚举:
enum class Status { Unknown, Created, Connected };
Status s = Status::Created;
enum class 和 enum struct 声明是等价的,这里我们使用 enum class。因为作用域枚举是受限制的命名空间,所以 C++20 标准允许我们将它们与 using 指令结合使用。你可以执行以下操作:
1) 使用 using 指令在局部作用域内引入作用域枚举标识符,如下所示:
int main()
{
using Status::Unknown;
Status s = Unknown;
}
2) 使用 using enum 指令在局部作用域内引入作用域枚举的所有标识符,如下所示:
struct foo
{
enum class Status { Unknown, Created, Connected };
using enum Status;
};
foo::Status s = foo::Created; // instead of foo::Status::Created
3) 在 switch 语句中,使用 using enum 指令引入作用域枚举标识符可以简化代码:
void process(Status const s)
{
switch (s)
{
using enum Status;
case Unknown: /*...*/ break;
case Created: /*...*/ break;
case Connected: /*...*/ break;
}
}
C++ enum class工作原理
enum 枚举对于开发人员来说会有一些问题:1) 它们把枚举器导出到周围的作用域(基于此,它们被称为无作用域枚举),所以就有以下两个缺陷:
- 如果同一命名空间中的两个枚举具有相同名称的枚举器,则可能导致命名冲突;
- 无法使用完全限定名称的枚举器。
enum Status { Unknown, Created, Connected };
enum Codes { OK, Failure, Unknown }; // error
auto status = Status::Created; // error
2) 在 C++11 之前,它们没法指定基础类型,必须是整型。类型不能大于 int,除非枚举器值不适应有符号或无符号整数。
基于此,不能前置枚举声明。原因是枚举的大小是未知的。这是因为在定义枚举器的值之前基础类型是未知的,这样编译器不知道选择哪些合适的整数类型。但是这个问题在 C++11 中得到了修复。
3) 枚举器的值会隐式转换为int。这意味着你可以有意或者无意地将有特定含义的枚举和整数混用(这甚至与枚举的含义无关),而且编译器不会发出警告。
enum Codes { OK, Failure };
void include_offset(int pixels) {/*...*/}
include_offset(Failure);
作用域枚举基本上是强类型枚举,其行为与无作用域枚举不同:
1) 它们不会把枚举器导出到周围的作用域。之前提到的两个枚举会变成如下这样,它们不再产生产生命名冲突并且可以使用完全限定名称的枚举器:
enum class Status { Unknown, Created, Connected };
enum class Codes { OK, Failure, Unknown }; // OK
Codes code = Codes::Unknown; // OK
2) 可以指定基础类型。除了作用域枚举可以指定基础类型之外,无作用域枚举中基础类型的规则同样适用于作用域枚举。这也解决了前置声明的问题,因为在定义可用之前就已经知道基础类型了:
enum class Codes : unsigned int;
void print_code(Codes const code) {}
enum class Codes : unsigned int
{
OK = 0,
Failure = 1,
Unknown = 0xFFFF0000U
};
3) 在作用域枚举中,枚举器的值不再隐式地转换为 int。除非指定了显式转换,否则将 enum class 的值赋给整数变量会导致编译错误:
Codes c1 = Codes::OK; // OK int c2 = Codes::Failure; // error int c3 = static_cast<int>(Codes::Failure); // OK
然而,作用域枚举也有缺陷:它们是受限制的命名空间。它们不会把它们的标识符导出到外面的作用域,这一点有时是不方便的。例如,如果你写了一个 switch 语句,就必须在每个 case 语句后面重复使用枚举名,下面的例子说明了这一点:
std::string_view to_string(Status const s)
{
switch (s)
{
case Status::Unknown: return "Unknown";
case Status::Created: return "Created";
case Status::Connected: return "Connected";
}
}
在 C++20 中,这可以通过使用具有作用域枚举名称的 using 指令来简化。上述代码可以简化为如下代码:
std::string_view to_string(Status const s)
{
switch (s)
{
using enum Status;
case Unknown: return "Unknown";
case Created: return "Created";
case Connected: return "Connected";
}
}
using 指令的作用是将所有标识符引入局部作用域,这样就可以在不用限定名称的情况下引用它们了。也可以使用 using 指令将具体的枚举器引入局部作用域,例如 using Status::Connected。
ICP备案:
公安联网备案: