11

Let's say I have an enumeration.

enum class ShapeName : char {TRIANGLE,CIRCLE,SQUARE};

and later I have a function like this:

void Function (ShapeName const shape){

    switch (shape){
        case ShapeName::TRIANGLE:
            DoSomething1();
            break;

        case ShapeName::CIRCLE:
            DoSomething2();
            break;

        case ShapeName::SQUARE: 
            DoSomething3();
            break;

        default:
            //THIS CODE BLOCK SHOULD NEVER BE EXECUTED!
    }

    return;
}

Although the default label should never be executed, I want to account for the potential bugs that may arise if a programmer adds another value to 'ShapeName' and it's not accounted for in the switch.
What would you recommended I do?

1. Assertions
I could use an assertion, but what am I asserting?

assert(false); //?

2. Exceptions
I could throw an exception, but I don't think that would be very good practice. I'm under the impression that exceptions are for run time events that can not be predicted because of certain environments.

3. Exits
I can just exit the program immediately with an error. That feels like the best idea, but I'm unsure if it's good practice. I thought the advantage of assertions was that you could turn them all off when you were ready ship the program. Then, all that assertion code would no longer exist.


Maybe there is another way that I'm not aware of. I do use a compiler flag that warns about unaccounted for values, but I still want to know what others recommend.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 4. Can you tell your compiler to warn for it? It's an option I'd expect for old-style enums. – Steve Jessop Apr 28 '12 at 02:20
  • @SteveJessop g++ -w -Wall -Wextra -Wswitch -Wswitch-default -Wswitch-enum -std=c++0x -o main main.cpp strange, I didn't get a warning at all with the above code. I added another ShapeName and got rid of the default label. Still nothing! – Trevor Hickey Apr 28 '12 at 02:30
  • Rats. C++11 implementation isn't finished, though, so we can live in hope. – Steve Jessop Apr 28 '12 at 02:37
  • possible duplicate of [Does "default" switch case disturb jump table optimization?](http://stackoverflow.com/questions/4278807/does-default-switch-case-disturb-jump-table-optimization) – Johannes Schaub - litb Apr 29 '12 at 11:59

4 Answers4

9

I like the idea of asserting with an informative message. Try this:

assert (!"The default case of so-so switch was reached.");

This always returns false, but provides a message you can use.

Edit:
I found the source I pulled this notion out of my memory from; it's in the following book:
C++ Coding Standards - 101 Rules and Guidelines

chris
  • 60,560
  • 13
  • 143
  • 205
  • 2
    Or if you don't like the way that's spelled, `assert(false && "message");` – Steve Jessop Apr 28 '12 at 02:25
  • 3
    And remember that asserts are usually stripped from the "release" builds. –  Apr 28 '12 at 02:30
  • 1
    For release builds, you can also use your compiler's equivalent of [`__assume(0)`](http://msdn.microsoft.com/en-us/library/1b3fsfxw.aspx) as an optimization hint. – ildjarn Apr 28 '12 at 02:36
  • Feels a little like a clever hack. Also feels like the best solution. Thanks for the [source](http://books.google.com/books?id=mmjVIC6WolgC&pg=PT426&lpg=PT426&dq=C%2B%2B+Coding+Standards+-+101+Rules+and+Guidelines+assert&source=bl&ots=ccRqIRdFZa&sig=kJZaGIPpWbxgyvd6i2mVuRrjCaQ&hl=en&sa=X&ei=qFmbT-PpD8a10QGijPGTDw&ved=0CCgQ6AEwAQ#v=onepage&q&f=false). – Trevor Hickey Apr 28 '12 at 02:56
  • 1
    I'm a fan of writing things like `assert(not "reachable");` – porglezomp Feb 17 '17 at 22:21
4

My preference for this specific case, where you're switching on an enum tag and handling all cases, is to leave out the default. That way, with any reasonable compiler, if someone adds a new tag to the enum and not to the switch, you'll get a compile-time warning about the tag not being handled in the switch.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Apparently GCC is unreasonable in respect of new C++11 enum classes, although the questioner doesn't say what version of GCC. – Steve Jessop Apr 28 '12 at 02:40
  • @SteveJessop gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 – Trevor Hickey Apr 29 '12 at 02:48
  • @SteveJessop: gcc (tried 4.4.5, 4.5.2, and 4.6.3) gives me "`warning: enumeration value ‘OTHER’ not handled in switch`" if I remove the default and add an `OTHER` tag... – Chris Dodd Jul 25 '12 at 18:02
0

I guess for me it would depend somewhat on what the DoSomething# methods do.

If it's going to cause a less graceful crash later you definitely want to interrupt runtime when this gets called with an explanation of "function called with invalid enum: enumName" or something to give the developer notification that they didn't update this switch statement, rather than leaving them wondering if something fails later.

enderland
  • 13,825
  • 17
  • 98
  • 152
0

Short answer: Use polymorphism to eliminate the problem.

Long answer: The easiest way to solve the root problem (i.e. How to prevent switch statements with missing entries?) would be to avoid using switch statements if there's a chance of entries being forgotten. Switch statements are useful in local contexts, but if the logic they represent is duplicated in multiple locations then there's a possibility of forgetting to update one and you're setting yourself up for runtime errors.

class Shape
{
    // functionality MUST be added for new shape types
    // or a compile error will occur
    virtual void DoSomething() const = 0;
};

void Function (Shape const & shape){
    return shape.DoSomething();
}

The switch statement may still be useful at the site of creation:

enum class ShapeName : char {TRIANGLE,CIRCLE,SQUARE};

unique_ptr< Shape > MakeShape (ShapeName const shape){
    switch (shape){
        case ShapeName::TRIANGLE:
            return unique_ptr< Shape >( new Triangle() );

        case ShapeName::CIRCLE:
            return unique_ptr< Shape >( new Circle() );

        case ShapeName::SQUARE: 
            return unique_ptr< Shape >( new Square() );
     }

     throw std::runtime_error();
     // or whichever option you prefer from the other answers
}

But that's the only place the logic exists. And even that can be improved upon with static polymorphism:

enum class ShapeName : char {TRIANGLE,CIRCLE,SQUARE,ELLIPSE};

template< ShapeName shape >
unique_ptr< Shape > MakeShape();

template<>
unique_ptr< Shape > MakeShape< ShapeName::TRIANGLE >()
{
    return unique_ptr< Shape >( new Triangle() );
}

template<>
unique_ptr< Shape > MakeShape< ShapeName::CIRCLE >()
{
    return unique_ptr< Shape >( new Circle() );
}

template<>
unique_ptr< Shape > MakeShape< ShapeName::SQUARE >()
{
    return unique_ptr< Shape >( new Square() );
}

int main()
{
    MakeShape< ShapeName::TRIANGLE >(); // okay

    MakeShape< ShapeName::ELLIPSE >(); // compile error
}

See Martin Folwer for more info.

Andrew Durward
  • 3,771
  • 1
  • 19
  • 30
  • Why a down-vote? I think for this contrived example, it is the best solution. Switch statements can be a code smell, for exactly this reason. The problem with `default` is you don't get a compiler warning for an omitted case; without `default` if you are in the sort of switch where each case does a return (e.g., factory), the compiler will complain about lack of return at end of function. I think MSVC once had an unreachable macro to help here. – Andrew Lazarus Dec 02 '14 at 23:42