tags:

views:

667

answers:

13

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?

Also, what if some idiot casts an arbitrary int to the enum type? Should this possibility even be considered? Or should we assume that such an egregious error would be caught in code review?

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;
    }
}
  • leave off the default case, gcc will warn you (will visual studio?)
  • add a default case with a assert(false);
  • add a default case that throws a catchable exception
  • add a default case that throws a non-catchable exception (it may just be policy to never catch it or always rethrow).
  • something better I haven't considered

I'm especially interested in why you choose to do it they way you do.

+17  A: 

I throw an exception. As sure as eggs are eggs, someone will pass an integer with a bad value rather than an enum value into your switch, and it's best to fail noisily but give the program the possibility of fielding the error, which assert() does not.

anon
+1: My thoughts exactly :-)
Jon Cage
Or someone will add a new enum constant.
Michael Myers
Do you throw a std::exception (or one of its derivatives) or something special? Do you have an exception class for 'Should never happen' bugs?
caspin
@Caspin All my libraries and apps throw an exception derived from std::exception specific to the library or app - but that's as far as I go. I am very opposed to creating complex exception hierarchies.
anon
+1 Excellent Use of exceptions...for something exceptional
gbrandt
seems like std::domain_error would be a good candidate to throw here
rmeador
BTW, i'm glad to congratulate you for hitting #1 on the ever-times c++ toplist. Have fun :)
Johannes Schaub - litb
@Johannes Thanks, but it's only because you seem to have severely cut back on your posting here - a sad loss to us all, IMHO
anon
A: 

I tend to do option 2:

add a default case that throws a catchable exception

That should highlight the problem if it ever occurs and it only costs you a couple of lines to imlpement.

Jon Cage
+1  A: 

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

Additional:

  • Make warnings to be treated as errors.
  • Add logging for default cases.
Mykola Golubyev
A: 

As a further option: Avoid switching over enums.

UncleBens
awesome answer. What would you propose instead?
caspin
Depends. Polymorphism, look-up tables?
UncleBens
I don't know why this was voted down. It is certainly not applicable in every case, but polymorphism, where appropriate, can make this a non-issue. In the second example in the question, you can make a class one and a class two, each derived from an abstract class base, with a do() method. Then instead of passing in e, you pass in a base* b, and call b->do(). One advantage is that if another option is added, you don't need to edit a switch, just add a new derived class.Like I said, this isn't isn't a universal solution, but where it does work, it works well.
KeithB
UncleBens should have put his alternatives in his answer, but Keith is correct. UncleBens shouldn't be voted down because this is a very good suggestion.
Merlyn Morgan-Graham
+5  A: 

First of all, I would always have a default in a switch statement. Even if there are no idiots around to cast integers to enums, there's always the possibility of memory corruption that the default can help to catch. For what it's worth, the MISRA rules make the presence of a default a requirement.

Regarding what you do, that depends on the situation. If an exception can be handled in a good way, handle it. If it's a state variable in a non-critical part of code, consider silently resetting the state variable to the initial state and carrying on (possibly logging the error for future reference). If it is going to cause the entire program to crash in a really messy way, try to fall over gracefully or something. In short, it all depends on what you're switching on and how bad an incorrect value would be.

Al
MISRA? That's your appeal to authority? Always having a default is a good practice, yes. You don't need to appeal to authority to express that.
jmucchiello
@jmecchiello: MISRA is recognized as well thought out standard for c++. Al is just stating that it's not only his opinion. It's only a fallacy to 'appeal to authority' if the authority is bogus, or possibly not qualified.
caspin
+12  A: 

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).

Johannes Schaub - litb
What about in release mode, when asserts are off? Do we just trust that assert will be hit in test? Leave asserts enabled in release mode? What about an "uncatchable" exception instead of an assert?
caspin
Whether or not it can be handled gracefully depends on the application. Consider a multi-threaded web server - if one thread uses an invalid enumeration value, is that a reason to terminate the entire server? Some might say "yes", but I would prefer to log the error, terminate the thread that caused it and continue. To enable that, I'd throw an exception, not call assert().
anon
@Caspin, you can't throw non-catchable exceptions in C++. Any one can be caught by `catch(...)`. In this case, yes, i will try to find the bugs during development. @Neil, I would definitely prefer to terminate the whole application, and fix the bug. Threads share the same address space, and once a thread goes crazy and be outside of what the programmer assumed when writing the code, we can't say anything anymore about the state of anything in the program. It doesn't help to say "oh, i hope the program continues correctly at least within the remaining threads."
Johannes Schaub - litb
This will give you a warning because of the lack of "return" in the default path.
Manuel
@Johannes Which is another reason I don't like assert() - using it means that debug and release are actually different code bases.
anon
@Manuel, fixed. I looked how the LLVM code-base implements this. It uses a quite similar technique to implement `llvm_unreachable`.
Johannes Schaub - litb
It's especially important for webservers, i think. Some guy figured out how to crash a thread on the server. If he's evil enough, he will do the same with the other threads, possibly figuring out how to do an exploit and get on some confidential data. If you shut it down right away, you will be offline for a few, but then be up and running again without security risks anymore.
Johannes Schaub - litb
@Johannes 'It doesn't help to say "oh, i hope the program continues correctly at least within the remaining threads."' Well, if the program is controlling a radiography machine, I agree. If it is serving a a site like Twitter, I'm less convinced.
anon
@Johannes My bottom line - throwing an exception is a more flexible solution - if you want termination semantics, simply don't catch the exception, or do catch it, log it and call exit().
anon
@Johannes - Clever, I'll probably use it in my own code.
Manuel
Asserting and throwing are not really mutually exclusive...
Nemanja Trifunovic
@Nemanja, in my opinion, they are in C++. But there are different opinions about this, apparently :)
Johannes Schaub - litb
@Johannes - VS2008 does not recognize "abort" as a termination path. exit(0) does the job, though.
Manuel
@Neil, I've asked this to Eric Lippert. See the comments to this question: http://stackoverflow.com/questions/990115/do-i-have-to-break-after-throwing-exception : "An exception which cannot be thrown is dead code, is untestable, and should be eliminated. So there you go: if the unexpected error is possible then throw an exception. If it is impossible then document that fact with an assertion, so that you'll be told if your supposition of impossibility was incorrect."
Johannes Schaub - litb
@Manuel. I see. Voted up your comment so people will know. Thanks!
Johannes Schaub - litb
@Johannes No offence, but why would I care what Eric Lippert (who he?) says? And in this case the exception certainly CAN be thrown, if an integer value is passed to the switch rather than an enumeration value, or if the enumeration is updated but the switch isn't.
anon
@Neil no worries. i'm not offended :) My understanding is that the function intends to cover all cases. However if it intends to cover all cases, then the default case is impossible to be reached. And in that event, an "exceptional case" cannot occur: The throw is then dead code. The writer of the code can claim this impossibility with an assertion.
Johannes Schaub - litb
+1, assertions are the right way to go. And I prefer to use `BOOST_ASSERT`. Then if you decide to use your library in a server that definitely must not terminate, you can define `boost::assertion_failed` function and throw exception from there.
avakar
@avakar, oh that's an interesting feature. Thanks!
Johannes Schaub - litb
+1  A: 

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

pm100
A: 

In cases similar to the example provided you can actually combine option 1 with one of the other options: Omit the default and enable the appropriate compiler warning if available. This way you find out immediately if a new enumerated value is added and you can conveniently add the case without having to guarantee that a specific code path executes to find it at runtime.

Then between the end of the switch and the end of the function add code to assert or throw (I prefer assert as this really is an invalid situation). This way if someone casts an int to your enum type you still get the runtime checking as well.

Mark B
+1  A: 

My $0.02:

If this method is externally visible (called by code that you don't control), then you probably need to handle the possibility of someone sending you an invalid enum value. In that case, throw an exception.

If this method is internal to your code (only you call it), then the assertion should be all that is necessary. This will catch the case of someday adding a new enum value and forgetting to update your switch statement.

Always provide a default case in every switch, and at the very least, assert if it gets hit. This habit will pay off by saving you hours or even days of head scratching every once in a while.

Scott Smith
A: 

I am not a C++ guy. But, in C#, I whould have written it like

enum Enum
{
    Enum_One,
    Enum_Two
};


Special make_special( Enum e )
{

    if(Enums.IsDefined(typeof(Enum),(int)e))
    {
       switch( e )
       {
          case Enum_One:
              return Special( /*stuff one*/ );
          case Enum_Two:
              return Special( /*stuff two*/ );
       }
    }
    // your own exception can come here.
    throw new ArgumentOutOfRangeException("Emum Value out of range");

}
Sumit Deo
How is that different than just having a default case with the throw? In the normal case, it checks the value of e if it is good and then puts it through the switch thus evaluating it twice. How is that better? And suppose Enum has defined value of Enum_Three. That will still end up being thrown as out of range. Is that intended?
jmucchiello
@Sumit: For those wary of perf issues, Enums.IsDefined uses reflection.
Johann Gerell
+2  A: 

As an additional remark (in addition to other responses) I'd like to note that even in C++ language with its relatively strict type-safety restrictions (at least compared to C), it is possible to generate a value of enum type that in general case might not match any of the enumerators, without using any "hacks".

If you have a enum type E, you can legally do this

E e = E();

which will initialize e with zero value. This is perfectly legal in C++, even if the declaration of E does not include a enumeration constant that stands for 0.

In other words, for any enum type E, the expression E() is well-formed and generates zero value of type E, regardless of how E is defined.

Note, that this loophole allows one to create a potentially "unexpected" enum value without using any "hacks", like a cast of an int value to enum type you mentioned in your question.

AndreyT
A: 

I personally recommend any of your solutions, except the first. Leave in the default case, and assert, throw (whatever exception type you like). In C#, Visual Studio doesn't warn you if you leave out the default case.

The reason I suggest you add the case and fail on it is that this is an important point of code maintenance. As soon as someone adds to the enumerated list, the switch statement has to grow to match.

Also (as UncleBen said), see if you can design against this whole scenario by using polymorphism. That way, when you add a new value to the enumeration, you only have to add it in one place. Any time you see a switch on an enum, you should consider using polymorphism instead.

Merlyn Morgan-Graham
A: 

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.

Snazzer