Question

Often in school, our lecturers will tell us to always include a Default statement at the end of a switch case statement. However, I have always been wondering is it necessary for ALL (or most) scenario?

Consider the following example in C++:

int num = rand()%3;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
}

In the case above, I feel that it is impossible to have a case where it can be > 2 or < 0, so do I still need to include a Default statement?

There are similar questions in SO asking for the need of Default in switch-case. The replies given there stated that we should almost at anytime include a Default. But of all the cases I encountered personally, it just seems to be redundant since the Default can never be reached.

Edit: Also, in terms of defensive programming, does this scenario needs a Default statement? And if I were to add a Default statement. It will only be a error-handling statement, am I right to say that?

Was it helpful?

Solution

Technically, no you don't, because you've covered all possible cases with your switch statement.

However, I always find it useful to include an assertion/exception in the default anyway. Consider the following scenario:

// V1.0.0: Initial version.
int num = rand()%3;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
}

Later...

// V1.0.0: Initial version.
// V1.0.1: Added a fourth method.
int num = rand()%4;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
}

In this scenario, developer #2 updated the rand modulus, but didn't actually add the case to handle num == 4. Without the default, you're going to silently fail, and that could cause all kinds of badness that could be very hard to debug. A more maintainable solution might be:

// V1.0.0: Initial version.
// V1.0.1: Added a fourth method.
int num = rand()%4;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
   default:
           assert(false);
           throw InvalidNumException("BUG: Method has not been specified for value of num");
}

When debugging, this would stop the debugger at the assert, and if (god forbid) the missing case makes it all the way to production, you'll get an exception thrown, rather than just running off and doing stuff that shouldn't happen.

EDIT:

I think including a catch-all is a good addition to a defensive programming style. It guarantees that you'll get a useful outcome if you miss a case statement (even if that useful outcome is to cause the program to crash).

EDIT 2:

As Andre Kostur mentioned in a comment to this answer, some compilers will emit warnings if you switch on an enum and forget to handle a case, which is a good reason to not include a default case for an enum switch statement. Refer to Phresnel's answer for more information about that.

OTHER TIPS

its not necessary, but it is a good habit to include it, with a print. I always do something like print "This should NOT happen" in the default (or except) so I know there is something happening which I didnt expect to happen. Computers can sometimes do weird things, be prepared!

Strictly speaking, you do not. However, in larger projects it can help finding or avoiding bugs if you want to change your code later.

For example, imagine you want to add some cases/methods later. If you leave the switch as is, you might have to debug and see why the new method isn't called. On the other hand, throwing a kind of NotImplementedException there will directly lead you to the fact that you forgot to add a case.

Depending on the exact context, it can be a matter of style. What I do sometimes is the following.

Consider that you in some point of time you tweak

int num = rand()%3;

to

int num = rand()%4;

then your switch statement is not correct and complete anymore. For such cases, you could add the following:

default:
    throw std::logic_error("Oh noes.");

std::logic_error is for errors by the programming team. Now if your team forgets to update the switch, it will (hopefully early) have an aborting program with a trace to hunt down for.

Downside of default

There is also a downside of including a default-clause. When you switch upon an enum ...

enum class Color {
    Red, Green, Blue
};

....

Color c = ....;
switch(c) {
case Color::Red: break; 
case Color::Green: break;
};

... some compilers will warn you that not all cases are covered. To shut the compiler up, you can now do two things:

Color c = ....;
switch(c) {
case Color::Red: break;
case Color::Green: break;
default: break;
};

Color c = ....;
switch(c) {
case Color::Red:  break;
case Color::Green:  break;
case Color::Blue:  break;
};

You will realise that of those two alternatives, the latter might be more productive. However, this relies on compiler behaviour. You could still throw an exception in the default, but transform what would be a nice compile time error into a runtime error, of which many agree the former is preferable.

The best of two worlds (portable errors, with the bonus of compile time errors) could be achieved using early exit or structures where you can test afterwards if a case was hit:

Color c = ....;
switch(c) {
case Color::Red: return;
case Color::Green: return;
};
throw std::logic_error(...);


MYSQL mysql = {0};
switch(c) {
case Color::Red: mysql = red_database(); break;
case Color::Green: mysql = green_database(); break;
};
if (!mysql)
    throw std::logic_error(...);

do I still need to include a default statement?

You don't need to, as long as the assumption about the possible range of num holds - which it will, as long as you don't change the code that calculates it.

You might want to, to validate that assumption in case it changes in future. That kind of defensive programming can be useful to catch broken assumptions quickly, before they cause major problems.

And if I were to add a Default statement. It will only be a error-handling statement, am I right to say that?

Yes. I'd throw a logic_error, or maybe just terminate the program, to indicate that a logical assumption is invalid.

So let's evolve your code a little bit, to something more realistic:

void processNumber(int maxNum) {
   int num = rand()%maxNum;
   switch (num)
   {
      case 0: methodOne();
           break;
      case 1: methodTwo();
           break;
      case 2: methodThree();
           break;
   }
}

and here you need to make sure that it is in the set of allowed values [1, 2, 3]. You can check it in number of ways, but you need safeguards and check input carefully and raise errors, even if it is internal function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top