我在一个项目中有这种情况,我们有一些套接字通信,主要交换字符进行流量控制。我们将这些角色转换为开关中的enum class : char
。我想知道,如果另一端发送的字符不在我们的枚举类中,可能会发生什么。
我有这个mwe:
enum class Foo : char {
UNKNOWN,
ENUM1 = 'A',
ENUM2 = 'B',
ENUM3 = 'C'
};
char bar1() {
return 'B';
}
char bar2() {
return 'D';
}
int main() {
switch((Foo)bar1()) {
case Foo::UNKNOWN:std::cout << "UNKNWON" << std::endl;break;
case Foo::ENUM1:std::cout << "ENUM1" << std::endl;break;
case Foo::ENUM2:std::cout << "ENUM2" << std::endl;break;
case Foo::ENUM3:std::cout << "ENUM3" << std::endl;break;
default:std::cout << "DEFAULT" << std::endl;break;
}
switch((Foo)bar2()) {
case Foo::UNKNOWN:std::cout << "UNKNWON" << std::endl;break;
case Foo::ENUM1:std::cout << "ENUM1" << std::endl;break;
case Foo::ENUM2:std::cout << "ENUM2" << std::endl;break;
case Foo::ENUM3:std::cout << "ENUM3" << std::endl;break;
default:std::cout << "DEFAULT" << std::endl;break;
}
return 0;
}
在这个例子中,我有一个带有未指定条目的enum class : char
和三个char分配的条目。当我运行它时,我收到的输出是
ENUM2
DEFAULT
这似乎完美无缺,因为未定义的示例只是跳转到默认情况。但是,这是“存钱吗”吗?我现在可能没有看到一些陷阱或其他并发症吗?
这是完全安全的,因为:
enum class
是一个范围的枚举;: char
;char
类型的值;这里的C ++ 17标准引用与上述语句相对应:
[dcl.enum] / 2 :( ...)enum-keys
enum class
和enum struct
在语义上是等价的;使用其中一个声明的枚举类型是作用域枚举,其枚举器是作用域枚举器。[dcl.enum] / 5 :( ...)每个枚举也有一个基础类型。可以使用enum-base显式指定基础类型。 (...)在这两种情况下,基础类型都被认为是固定的。 (......)
[dcl.enum] / 8:对于其基础类型是固定的枚举,枚举的值是基础类型的值。 (......)
[expr.static.cast] / 10可以将整数或枚举类型的值显式转换为完整的枚举类型。如果枚举类型具有固定的基础类型,则首先通过整数转换将值转换为该类型(如果需要),然后再转换为枚举类型。 [expr.cast] / 4 const_cast,static_cast,static_cast后跟const_cast,reinterpret_cast,reinterpret_cast后跟const_cast执行的转换可以使用显式类型转换的强制转换表示法执行。 (...)如果转换可以用上面列出的多种方式解释,则使用列表中首先出现的解释(...)
如果基础类型不能修复,结论会有所不同。在这种情况下,[dcl.enum] / 8的剩余部分将适用:它或多或少地说,如果你不在枚举的最小和最大枚举数之内,你不确定该值是否可以表示。
另请参阅问题Is it allowed for an enum to have an unlisted value?,它更通用(C ++和C)但不使用作用域枚举或指定的基础类型。
这里有一个代码片段,用于使用没有定义枚举数的枚举值:
switch((Foo)bar2()) {
case Foo::UNKNOWN: std::cout << "UNKNWON" << std::endl;break;
case Foo::ENUM1: std::cout << "ENUM1" << std::endl;break;
case Foo::ENUM2: std::cout << "ENUM2" << std::endl;break;
case Foo::ENUM3: std::cout << "ENUM3" << std::endl;break;
case static_cast<Foo>('D'): std::cout << "ENUM-SPECIAL-D" << std::endl;break;
default: std::cout << "DEFAULT" << std::endl;break;
}
这不完全安全。我发现C ++ Standard,[expr.static.cast],第10段,陈述如下:
可以将整数或枚举类型的值显式转换为枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。否则,结果值未指定(可能不在该范围内)。浮点类型的值也可以显式转换为枚举类型。结果值与将原始值转换为枚举的基础类型(4.9)以及随后的枚举类型相同。
7.2部分解释了如何确定限制:
对于其基础类型是固定的枚举,枚举的值是基础类型的值。否则,对于枚举,其中emin是最小的枚举数且emax是最大的,枚举的值是bmin到bmax范围内的值,定义如下:令两个补码表示为K,一个为0补码或符号幅度表示。 bmax是大于或等于max(| emin | - K,| emax |)且等于2M-1的最小值,其中M是非负整数。如果emin是非负的,则bmin为零,否则为 - (bmax + K)。如果bmin为零,则大小足以容纳枚举类型的所有值的最小位字段的大小为max(M,1),否则为M + 1。可以定义具有未由其任何枚举器定义的值的枚举。如果枚举器列表为空,则枚举的值就像枚举具有值为0的单个枚举器一样。
因此,如果它在范围内,它可能会将未定义的值转换为枚举,但如果不是,那么它是未定义的。从理论上讲,人们可以定义一个具有较大值的枚举,并确保然后投射可以工作,但是向后投射,从枚举到整数类型并进行比较可能会更好。