What's the best practice for using a switch
statement vs using an if
statement for 30 unsigned
enumerations where about 10 have an expected action (that presently is the same action). Performance and space need to be considered but are not critical. I've abstracted the snippet so don't hate me for the naming conventions.
switch
statement:
// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing
switch (numError)
{
case ERROR_01 : // intentional fall-through
case ERROR_07 : // intentional fall-through
case ERROR_0A : // intentional fall-through
case ERROR_10 : // intentional fall-through
case ERROR_15 : // intentional fall-through
case ERROR_16 : // intentional fall-through
case ERROR_20 :
{
fire_special_event();
}
break;
default:
{
// error codes that require no additional action
}
break;
}
if
statement:
if ((ERROR_01 == numError) ||
(ERROR_07 == numError) ||
(ERROR_0A == numError) ||
(ERROR_10 == numError) ||
(ERROR_15 == numError) ||
(ERROR_16 == numError) ||
(ERROR_20 == numError))
{
fire_special_event();
}
When it comes to compiling the program, I don't know if there is any difference. But as for the program itself and keeping the code as simple as possible, I personally think it depends on what you want to do. if else if else statements have their advantages, which I think are:
allow you to test a variable against specific ranges you can use functions (Standard Library or Personal) as conditionals.
(example:
However, If else if else statements can get complicated and messy (despite your best attempts) in a hurry. Switch statements tend to be clearer, cleaner, and easier to read; but can only be used to test against specific values (example:
I prefer if - else if - else statements, but it really is up to you. If you want to use functions as the conditions, or you want to test something against a range, array, or vector and/or you don't mind dealing with the complicated nesting, I would recommend using If else if else blocks. If you want to test against single values or you want a clean and easy to read block, I would recommend you use switch() case blocks.
Compilers are really good at optimizing
switch
. Recent gcc is also good at optimizing a bunch of conditions in anif
.I made some test cases on godbolt.
When the
case
values are grouped close together, gcc, clang, and icc are all smart enough to use a bitmap to check if a value is one of the special ones.e.g. gcc 5.2 -O3 compiles the
switch
to (and theif
something very similar):Notice that the bitmap is immediate data, so there's no potential data-cache miss accessing it, or a jump table.
gcc 4.9.2 -O3 compiles the
switch
to a bitmap, but does the1U<<errNumber
with mov/shift. It compiles theif
version to series of branches.Note how it subtracts 1 from
errNumber
(withlea
to combine that operation with a move). That lets it fit the bitmap into a 32bit immediate, avoiding the 64bit-immediatemovabsq
which takes more instruction bytes.A shorter (in machine code) sequence would be:
(The failure to use
jc fire_special_event
is omnipresent, and is a compiler bug.)rep ret
is used in branch targets, and following conditional branches, for the benefit of old AMD K8 and K10 (pre-Bulldozer): What does `rep ret` mean?. Without it, branch prediction doesn't work as well on those obsolete CPUs.bt
(bit test) with a register arg is fast. It combines the work of left-shifting a 1 byerrNumber
bits and doing atest
, but is still 1 cycle latency and only a single Intel uop. It's slow with a memory arg because of its way-too-CISC semantics: with a memory operand for the "bit string", the address of the byte to be tested is computed based on the other arg (divided by 8), and isn't limited to the 1, 2, 4, or 8byte chunk pointed to by the memory operand.From Agner Fog's instruction tables, a variable-count shift instruction is slower than a
bt
on recent Intel (2 uops instead of 1, and shift doesn't do everything else that's needed).If your cases are likely to remain grouped in the future--if more than one case corresponds to one result--the switch may prove to be easier to read and maintain.
The Switch, if only for readability. Giant if statements are harder to maintain and harder to read in my opinion.
ERROR_01 : // intentional fall-through
or
(ERROR_01 == numError) ||
The later is more error prone and requires more typing and formatting than the first.
The switch is faster.
Just try if/else-ing 30 different values inside a loop, and compare it to the same code using switch to see how much faster the switch is.
Now, the switch has one real problem : The switch must know at compile time the values inside each case. This means that the following code:
won't compile.
Most people will then use defines (Aargh!), and others will declare and define constant variables in the same compilation unit. For example:
So, in the end, the developper must choose between "speed + clarity" vs. "code coupling".
(Not that a switch can't be written to be confusing as hell... Most the switch I currently see are of this "confusing" category"... But this is another story...)
.
Aesthetically I tend to favor this approach.
Make the data a little smarter so we can make the logic a little dumber.
I realize it looks weird. Here's the inspiration (from how I'd do it in Python):