C ++枚举类:转换为非现有条目

问题描述 投票:2回答:2

我在一个项目中有这种情况,我们有一些套接字通信,主要交换字符进行流量控制。我们将这些角色转换为开关中的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

这似乎完美无缺,因为未定义的示例只是跳转到默认情况。但是,这是“存钱吗”吗?我现在可能没有看到一些陷阱或其他并发症吗?

c++ enums undefined-behavior enum-class
2个回答
2
投票

这是完全安全的,因为:

  • 你的enum class是一个范围的枚举;
  • 你的枚举有一个固定的基础类型: char;
  • 所以枚举的值是char类型的值;
  • 因此,对枚举的char值的强制转换是完全有效的。

这里的C ++ 17标准引用与上述语句相对应:

[dcl.enum] / 2 :( ...)enum-keys enum classenum 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;
}

1
投票

这不完全安全。我发现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的单个枚举器一样。

因此,如果它在范围内,它可能会将未定义的值转换为枚举,但如果不是,那么它是未定义的。从理论上讲,人们可以定义一个具有较大值的枚举,并确保然后投射可以工作,但是向后投射,从枚举到整数类型并进行比较可能会更好。

© www.soinside.com 2019 - 2024. All rights reserved.