Using default in a switch statement when switching over an enum

后端 未结 16 1254
一个人的身影
一个人的身影 2020-12-15 02:57

What is your procedure when switching over an enum where every enumeration is covered by a case? Ideally you\'d like the code to be future proof, how do you do that?

<
相关标签:
16条回答
  • 2020-12-15 03:47

    Apart from exceptions which would be the preferred solution at runtime (and will handle crazy casting), I tend to use static compile time assertions as well.

    You can do the following:

    //this fails at compile time when the parameter to the template is false at compile time, Boost should provide a similar facility
    template <COLboolean IsFalse> struct STATIC_ASSERTION_FAILURE;
    template <> struct STATIC_ASSERTION_FAILURE<true>{};
    #define STATIC_ASSERT( CONDITION_ ) sizeof( STATIC_ASSERTION_FAILURE< (COLboolean)(CONDITION_) > );
    
    //
    // this define will break at compile time at locations where a switch is being
    // made for the enum. This helps when adding enums
    //
    // the max number here should be updated when a new enum is added.
    //
    // When a new enum is added, at the point of the switch (where this
    // define is being used), update the switch statement, then update
    // the value being passed into the define.
    //
    #define ENUM_SWITCH_ASSERT( _MAX_VALUE )\
       STATIC_ASSERT( _MAX_VALUE  ==  Enum_Two)
    
    enum Enum
    {
        Enum_One = 0,
        Enum_Two = 1
    };
    

    Then in your code, whenever you use the enum set:

    ENUM_SWITCH_ASSERT( Enum_Two )
    switch( e )
    {
        case Enum_One:
            do_one();
            break;
        case Enum_Two:
            do_two();
            break;
    }
    

    Now whenever you change the macro ENUM_SWITCH_ASSERT to handle a new enum value, it will break at compile time near locations that use the enum set. Helps lots when adding new cases.

    0 讨论(0)
  • 2020-12-15 03:48

    In addition to the advise to throw an exception, if you use gcc, you can use the -Wswitch-enum (and eventually -Werror=switch-enum) which will add a warning (or an error) if a member of you enum doesn't appear in any case. There may be an equivalent for other compilers but I only use it on gcc.

    0 讨论(0)
  • 2020-12-15 03:50

    assert and then maybe throw.

    For in-house code thats in the same project as this (you didnt say what the function boundary is - internal lib, external lib, inside module,...) it will assert during dev. THis is what you want.

    If the code is for public consumption (other teams, for sale etc) then the assert will disappear and you are left with throw. THis is more polite for external consumers

    If the code is always internal then just assert

    0 讨论(0)
  • 2020-12-15 03:52

    I would put an 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!");
        }
    }
    

    Not handling an enumeration value, while the intention is to cover all cases, is an error in the code that needs to be fixed. The error can't be resolved from nor handled "gracefully", and should be fixed right away (so i would not throw). For having the compiler be quiet about "returning no value" warnings, call abort, like so

    #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!");
        }
    }
    

    No warning by the compiler anymore about a return without value, because it knows abort never returns. We immediately terminate the failing program, which is the only reasonable reaction, in my opinion (there is no sense in trying to continue to run a program that caused undefined behavior).

    0 讨论(0)
  • 2020-12-15 03:52

    LLVM Coding Standards' opinion is: Don’t use default labels in fully covered switches over enumerations

    Their rationale is:

    -Wswitch warns if a switch, without a default label, over an enumeration does not cover every enumeration value. If you write a default label on a fully covered switch over an enumeration then the -Wswitch warning won’t fire when new elements are added to that enumeration. To help avoid adding these kinds of defaults, Clang has the warning -Wcovered-switch-default which is off by default but turned on when building LLVM with a version of Clang that supports the warning.

    Based on this , I personally like doing:

    enum MyEnum { A, B, C };
    
    int func( MyEnum val )
    {
        boost::optional<int> result;  // Or `std::optional` from Library Fundamental TS
    
        switch( val )
        {
        case A: result = 10; break;
        case B: result = 20; break;
        case C: result = 30; break;
        case D: result = 40; break;
        }
    
        assert( result );  // May be a `throw` or any other error handling of choice
    
        ...  // Use `result` as seen fit
    
        return result;
    }
    

    If you choose to return on every case of the switch, you don't need boost::optional: simply call std::abort() or throw unconditionally just after the switch block.

    It's nonetheless important to remember that maybe switch is not the best design tool to begin with (as already stated in @UncleBens answer): Polymorphism or some type of lookup-table could place a better solution, especially if your enum has a lot of elements.

    PS: As a curiosity, the Google C++ Style Guide makes an exception to switchs-over-enums not to have default cases:

    If not conditional on an enumerated value, switch statements should always have a default case

    0 讨论(0)
  • 2020-12-15 03:53

    Your items are good. But I'd remove 'throw catchable exception'.

    Additional:

    • Make warnings to be treated as errors.
    • Add logging for default cases.
    0 讨论(0)
提交回复
热议问题