转换枚举中整数的安全方法

Safe way to convert an integer in an enum

本文关键字:安全 方法 整数 枚举 转换      更新时间:2023-10-16

如果我将一个整数强制转换到枚举类中,但该值不在枚举中,会发生什么?例如:我想要一个函数来测试一个整数是否有来自枚举类的值:

enum class EnumClass { A, B = 4, C = 9, D = 60 };
bool checkEnumClass( int v )
{
    switch( static_cast< EnumClass >( v ) )
    {
    case EnumClass::A:
    case EnumClass::B:
    case EnumClass::C:
    case EnumClass::D:
        return true;
    default:
        return false;
    }
}
checkEnumClass( 0 ) == true;
checkEnumClass( 7 ) == false;   // is this true?

这是检查整数是否可转换为枚举的正确方法吗?

我没有看到比OP提供的解决方案更好的根本解决方案。然而,它有一个小缺陷,我可以建议(非标准)解决方案。

问题如下。假设今天的代码与OP中的代码相同,但有一天,有人向EnumClass添加了一个新的枚举器,该枚举器变为:

enum class EnumClass { A, B = 4, C = 9, D = 60, E = 70 };

还假设这个人忘记更新checkEnumClass的定义(这不太可能发生,尤其是当代码在另一个文件中时)。然后,

checkEnumClass( 70 );

将返回CCD_ 3,尽管70现在是有效值。单元测试可能有助于捕捉这个错误,但用户必须记住更新测试。(回想一下,他们一开始就忘了更新代码!)

不幸的是,标准C++没有提供一种方法来强制enum上的switch覆盖所有情况(不像D提供final switch语句)。

但是,有一些编译器特定的功能可以为您做到这一点。

对于GCC(我相信还有Clang),您可以添加编译器选项-Wswitch(或-Wall,意味着-Wswitch)。对于Visual Studio,您可以添加

#pragma warning(error : 4062)

到包含checkEnumClass的文件(而不是包含枚举定义的文件)

最后,您必须稍微更改checkEnumClass,因为default标签告诉编译器所有情况都已涵盖。代码应该是这样的:

bool checkEnumClass( int v )
{
    switch( static_cast< EnumClass >( v ) )
    {
    case EnumClass::A:
    case EnumClass::B:
    case EnumClass::C:
    case EnumClass::D:
        return true;
    }
    return false;
}

使用此解决方法,包含枚举器E但忘记相应更新checkEnumClass的人员将收到以下错误/警告:

GCC:

警告:枚举值"E"未在交换机[-Wswitch]中处理

Visual Studio:

错误C4062:未处理枚举"EnumClass"的开关中的枚举器"E"
交换机(static_cast<EnumClass>(v))

更新1:根据elvis.dukaj.的评论

-Werror添加到GCC的选项中,将所有警告转化为错误,这是一个很好的做法。

更新2:比-Wswitch更好的是-Wswitch-enum,即使有default标签,它也会引发警告(如果是-Werror,则会引发错误)。不幸的是,我不知道Visual Studio中有任何类似的功能。

枚举可以容纳最小值和最大值之间的任何值,因此您所拥有的基本正确。您需要额外做的唯一一件事是确保integer参数在正确的范围内,因为如果您试图强制转换枚举范围之外的int,则会出现未定义的行为:

bool checkEnumClass( int v )
{
    if (v < static_cast<int>(EnumClass::A)) return false;
    if (v > static_cast<int>(EnumClass::D)) return false;
    switch( static_cast< EnumClass >( v ) )
    {
    case EnumClass::A:
    case EnumClass::B:
    case EnumClass::C:
    case EnumClass::D:
        return true;
    default:
        return false;
    }
}

如果您需要在编译时检查枚举值,您可以尝试以下操作:

template <int I> struct check_enum { static const bool value = false; };
template <> struct check_enum<static_cast<int>(EnumClass::A)>
{ static const bool value = true; };
template <> struct check_enum<static_cast<int>(EnumClass::B)>
{ static const bool value = true; };
template <> struct check_enum<static_cast<int>(EnumClass::C)>
{ static const bool value = true; };
template <> struct check_enum<static_cast<int>(EnumClass::D)>
{ static const bool value = true; };

然后,你可以这样使用它:

static_assert(check_enum<0>::value, "invalid enum value"); // ok!
static_assert(check_enum<1>::value, "invalid enum value"); // compile error

现场演示。

编辑:对于C++14模板变量,同样的方法也是可行的。

template <int I> constexpr bool check_enum = false;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::A)> = true;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::B)> = true;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::C)> = true;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::D)> = true;
static_assert(check_enum<0>, "invalid enum value"); // ok!
static_assert(check_enum<1>, "invalid enum value"); // compile error

这些方法的主要缺点是努力专门化每一个价值,你必须思考这些努力是否值得。如果错过了一些值,那么可能很难找到并解决问题。

只需检查int是否不大于检查类中可能的最大值,不需要switch语句,只需使用if语句,或者更好的是,只使用bool。

bool checkEnumClass(int i)
{
    return (i <= 7);
}