切换枚举时在switch语句中使用default

def*_*ode 33 c++ enums switch-statement

切换枚举时,您的程序是什么?每个枚举都包含在案例中?理想情况下,您希望代码能够成为未来的证明,您是如何做到的?

另外,如果一些白痴向枚举类型转换了一个任意的int呢?是否应该考虑这种可能性?或者我们是否应该假设在代码审查中会发现这样一个令人震惊的错误?

enum Enum
{
    Enum_One,
    Enum_Two
};

Special make_special( Enum e )
{
    switch( e )
    {
        case Enum_One:
            return Special( /*stuff one*/ );

        case Enum_Two:
            return Special( /*stuff two*/ );
    }
}

void do_enum( Enum e )
{
    switch( e )
    {
        case Enum_One:
            do_one();
            break;

        case Enum_Two:
            do_two();
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 离开默认情况,gcc会警告你(视觉工作室会吗?)
  • 添加一个默认情况assert(false);
  • 添加一个抛出可捕获异常的默认情况
  • 添加一个抛出不可捕获异常的默认情况(可能只是策略从不捕获它或总是重新抛出).
  • 我没有考虑过的更好的东西

我特别感兴趣的是你为什么选择这样做.

小智 30

我抛出一个例外.正如鸡蛋是鸡蛋一样,有人会将一个带有错误值而不是枚举值的整数传递给你的交换机,最好是吵闹,但让程序有可能出现错误,而assert()则没有.

  • 或者有人会添加一个新的枚举常量. (7认同)
  • @Johannes谢谢,但这只是因为你似乎严重削减了你在这里的帖子 - 给我们所有人带来了悲伤,恕我直言 (5认同)
  • @Caspin我的所有库和应用程序抛出一个异常,该异常派生自特定于库或应用程序的std :: exception - 但这就是我的目标.我非常反对创建复杂的异常层次结构. (2认同)
  • 看起来std :: domain_error似乎是一个很好的候选人 (2认同)

Joh*_*itb 23

我会放一个assert.

Special make_special( Enum e )
{
    switch( e )
    {
        case Enum_One:
            return Special( /*stuff one*/ );

        case Enum_Two:
            return Special( /*stuff two*/ );

        default:
            assert(0 && "Unhandled special enum constant!");
    }
}
Run Code Online (Sandbox Code Playgroud)

不处理枚举值,而意图是涵盖所有情况,是代码中的错误需要修复.错误无法从"优雅"解决或处理,应立即修复(所以我不会抛出).为了使编译器对"返回无值"警告保持安静,请调用abort,如此

#ifndef NDEBUG
#define unreachable(MSG) \
  (assert(0 && MSG), abort())
#else
#define unreachable(MSG) \
  (std::fprintf(stderr, "UNREACHABLE executed at %s:%d\n", \
                __FILE__, __LINE__), abort())
#endif 

Special make_special( Enum e )
{
    switch( e )
    {
        case Enum_One:
            return Special( /*stuff one*/ );

        case Enum_Two:
            return Special( /*stuff two*/ );

        default:
            unreachable("Unhandled special enum constant!");
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器没有关于没有值的返回的警告,因为它知道abort永远不会返回.在我看来,我们立即终止失败的程序,这是唯一合理的反应(试图继续运行导致未定义行为的程序是没有意义的).

  • 是否可以优雅地处理它取决于应用程序.考虑一个多线程Web服务器 - 如果一个线程使用无效的枚举值,这是终止整个服务器的原因吗?有些人可能会说"是",但我更愿意记录错误,终止导致它的线程并继续.为了实现这一点,我抛出一个异常,而不是调用assert(). (3认同)
  • @Caspin,你不能在C++中抛出不可捕获的异常.任何人都可以被`catch(...)`抓住.在这种情况下,是的,我会尝试在开发过程中找到错误.@Neil,我肯定更愿意终止整个应用程序,并修复bug.线程共享相同的地址空间,一旦线程变得疯狂并且超出了程序员在编写代码时所假设的内容,我们就不能再对程序中的任何内容的状态说些什么了.它说"哦,我希望程序至少在其余的线程中继续正确"是没有用的. (3认同)
  • @Neil,我问过Eric Lippert.请参阅此问题的评论:http://stackoverflow.com/questions/990115/do-i-have-to-break-after-throwing-exception:"无法抛出的异常是死代码,不可测试,以及应该被淘汰.所以你去:如果意外的错误是可能的,那么抛出异常.如果不可能,那么用断言记录这个事实,这样你就会被告知你的不可能性是不正确的." (3认同)
  • +1,断言是正确的方法.我更喜欢使用`BOOST_ASSERT`.然后,如果您决定在绝对不能终止的服务器中使用您的库,您可以定义`boost :: assertion_failed`函数并从那里抛出异常. (3认同)
  • @Johannes - VS2008不会将"中止"识别为终止路径.但是,exit(0)完成了这项工作. (2认同)
  • @Neil不用担心。我并不冒犯:)我的理解是该功能旨在涵盖所有情况。但是,如果打算涵盖所有情况,则不可能达到默认情况。在这种情况下,“异常情况”就不会发生:抛出就是死代码。代码的编写者可以通过断言声明这种可能性。 (2认同)

DrA*_*rAl 7

首先,我会一直default一个switch说法.即使没有白痴将integers投射到enums,也总是存在内存损坏的可能性,这default有助于捕获.对于它的价值,MISRA规则规定了默认值的要求.

关于你做什么,这取决于具体情况.如果可以很好地处理异常,请处理它.如果它是代码的非关键部分中的状态变量,请考虑将状态变量静默地重置为初始状态并继续(可能记录错误以供将来参考).如果它会导致整个程序以一种非常混乱的方式崩溃,那么试着优雅或者某种方式.简而言之,这一切都取决于你正在做什么switch以及不正确的价值有多糟糕.

  • @jmecchiello:MISRA被认为是c ++的经过深思熟虑的标准.Al只是说这不仅仅是他的意见.如果权威是虚假的,或者可能没有资格,那么"诉诸权威"只是一种谬论. (5认同)

AnT*_*AnT 7

作为补充说明(除了其他回复之外),我还要注意,即使在C++语言中,它具有相对严格的类型安全限制(至少与​​C相比),也可以生成枚举类型的值.一般情况可能与任何枚举者都不匹配,而不使用任何"黑客".

如果您有枚举类型E,您可以合法地执行此操作

E e = E();
Run Code Online (Sandbox Code Playgroud)

这将初始化e为零值.这在C++中是完全合法的,即使声明E中没有包含枚举常量0.

换句话说,对于任何枚举类型E,表达式E()都是格式良好的,并且E无论如何E定义,都会生成零值类型.

请注意,这个漏洞允许人们在不使用任何"黑客"的情况下创建潜在的"意外"枚举值,例如int您在问题中提到的枚举类型的值.