Sign in to follow this  
homi576

c++ should nested switch statements be avoided?

Recommended Posts

Hay everyone

 

  I have a query and i think you guys are the best to ask about this, is it good or bad practice to use nested switch statements? I know c++ allows up to 256 levels of nesting but is it a good idea?

 

   It looks quite messy and difficult to read so I was wondering if it was a good idea, also can anyone suggest a program or code that would utilize the full 256 levels or close to that c++ allows.

Share this post


Link to post
Share on other sites
Here's what I do for my massively nested switch statements in my Intel x86-64 disassembler (which has five or six nested levels of switch statements)

1. Each nested switch goes in a separate function. A case statement in the first switch calls a function with another switch in it (if necessary - not all instructions nest all the way down).


2. The Intel instructions have this really tedious-to-enter prefix pattern for the SSE/AVX instructions:

The instruction's 66, F3, and F2 prefix(es) can be combined to change the instruction mnemonic entirely (in addition to the typical three opcode bytes).

Due to how the Intel manual's opcode map is laid out (the 66/F3/F2 prefixes are arranged in rows), in the 0F38xx and 0F3Axx switch tables, I do this (this is technically C# but the syntax is nearly identical):

Instead of switch once on the opcodeByte3 and then switch again on the SimdType, making one big 256-entry switch followed by 256x 4-entry switch functions (which would have no longer lined up nicely with the Intel manual), I rearrange the bits. I still switch twice, but the adjusted arrangement is more readable.

 
opcodeByte3 = reader.ReadByte();
            
// Encode the switch to make cases line up more exactly with the manual.
int row = opcodeByte3 & 0xF0;

switch (simdType)
{
    case SimdType.None: break;
    case SimdType.S_66: row += 1; break;
    case SimdType.S_F3: row += 2; break;
    case SimdType.S_F2: row += 3; break;
    case SimdType.F266: row += 4; break;
}

// This switch ignores the low 3 bits in order to map directly to rows.
// Case statements are be the leftmost column of each page.
switch (row)
{
    case 0x00: return FindOpcode0F38_Row0_None();
    case 0x01: return FindOpcode0F38_Row0_66();
    case 0x10: return FindOpcode0F38_Row1_None();
    case 0x11: return FindOpcode0F38_Row1_66();
    case 0x21: return FindOpcode0F38_Row2_66();
    case 0x31: return FindOpcode0F38_Row3_66();
    case 0x41: return FindOpcode0F38_Row4_66();
    case 0x51: return FindOpcode0F38_Row5_66();
                
    case 0x71: return FindOpcode0F38_Row7_66();
    case 0x81: return FindOpcode0F38_Row8_66();
    case 0x91: return FindOpcode0F38_Row9_66();
    case 0xA1: return FindOpcode0F38_RowA_66();
    case 0xB1: return FindOpcode0F38_RowB_66();
                
    case 0xD1: return FindOpcode0F38_RowD_66();
                
    case 0xF0: return FindOpcode0F38_RowF_None();
    case 0xF1: return FindOpcode0F38_RowF_66();
    case 0xF2: return FindOpcode0F38_RowF_F3();
    case 0xF3: return FindOpcode0F38_RowF_F2();
    case 0xF4: return FindOpcode0F38_RowF_66F2();
}
Each individual row function looks like this:
 
private bool FindOpcode0F38_Row0_None()
{
    switch (opcodeByte3)
    {
        case 0x00: return Valid(Opcode.PSHUFB, Pq, Qq);
        case 0x01: return Valid(Opcode.PHADDW, Pq, Qq);
        case 0x02: return Valid(Opcode.PHADDD, Pq, Qq);
        case 0x03: return Valid(Opcode.PHADDSW, Pq, Qq);
        case 0x04: return Valid(Opcode.PMADDUBSW, Pq, Qq);
        case 0x05: return Valid(Opcode.PHSUBW, Pq, Qq);
        case 0x06: return Valid(Opcode.PHSUBD, Pq, Qq);
        case 0x07: return Valid(Opcode.PHSUBSW, Pq, Qq);

        case 0x08: return ValidSimd(Opcode.PSIGNB, Pq, Qq);
        case 0x09: return ValidSimd(Opcode.PSIGNW, Pq, Qq);
        case 0x0A: return ValidSimd(Opcode.PSIGND, Pq, Qq);
        case 0x0B: return ValidSimd(Opcode.PMULHRSW, Pq, Qq);
    }
            
    return false;
}

private bool FindOpcode0F38_Row0_66()
{
    switch (opcodeByte3)
    {
        case 0x00: return ValidSV13(Opcode.VPSHUFB, Vx, Hx, Wx);
        case 0x01: return ValidSV13(Opcode.VPHADDW, Vx, Hx, Wx);
        case 0x02: return ValidSV13(Opcode.VPHADDD, Vx, Hx, Wx);
        case 0x03: return ValidSV13(Opcode.VPHADDSW, Vx, Hx, Wx);
        case 0x04: return ValidSV13(Opcode.VPMADDUBSW, Vx, Hx, Wx);
        case 0x05: return ValidSV13(Opcode.VPHSUBW, Vx, Hx, Wx);
        case 0x06: return ValidSV13(Opcode.VPHSUBD, Vx, Hx, Wx);
        case 0x07: return ValidSV13(Opcode.VPHSUBSW, Vx, Hx, Wx);

        case 0x08: return ValidSV13(Opcode.VPSIGNB, Vx, Hx, Wx);
        case 0x09: return ValidSV13(Opcode.VPSIGNW, Vx, Hx, Wx);
        case 0x0A: return ValidSV13(Opcode.VPSIGND, Vx, Hx, Wx);
        case 0x0B: return ValidSV13(Opcode.VPMULHRSW, Vx, Hx, Wx);
        case 0x0C: return ValidVexOnly(Opcode.VPERMILPS, Vx, Hx, Wx);
        case 0x0D: return ValidVexOnly(Opcode.VPERMILPD, Vx, Hx, Wx);
        case 0x0E: return ValidVexOnly(Opcode.VTESTPS, Vx, Wx);
        case 0x0F: return ValidVexOnly(Opcode.VTESTPD, Vx, Wx);
    }
            
    return false;
}
Edited by Nypyren

Share this post


Link to post
Share on other sites

I know c++ allows up to 256 levels of nesting but is it a good idea?

C++ doesn't provide policies or judgements on good/bad practices.
It provides raw functionality, and you or your team have to decide how to use the language. Typically this means you allow less in your code than the language allows.

Other languages have different ideas about such things. Python aims to have one obvious way to do each thing, Java even forbids things that the designers thought to be bad.

Share this post


Link to post
Share on other sites

>>   c++ should nested switch statements be avoided?

 

only for the sake of read-ability.

 

un-ordered switch statements can be faster than if-then-else statements.

 

ordered switch statements can be even faster.

 

a switch is too big to inline if you put a second level switch in a function call.

 

so a second or lower level switch in a separate will cost you a function call.  with register passing and stack frame pointers omitted, this is not much of a hit.

 

i personally place lower level switches in separate functions if they are large (more than 3 cases), or if the whole shebang get bigger than about 2 screens.

 

in general, having each case call a function yields the cleanest looking code. whether that case is another switch or not. inline all appropriate, register passing, and no frame pointers help keep the overhead of doing this low.

 

nested menus systems with switch statements is a place where you can get very deep nesting. some of the action menus in Caveman are nested 4 or 5, maybe even 6 levels deep.

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

If you are indeed using C++, you should not need to use a nested switch statement. What do you need such a thing for I wonder?

 

I read that you can, and should, replace complicated if-else and switch statements with an object-oriented solution such as polymorphism. You would have only one switch and that should be used to change the class of an object when needed.

 

Example

class Sword {
    public:
        Sword(){strength = 25;}
        int getStrength(){return strength;}
    protected:
        int strength;
};

class Excalibur : public Sword {
    public: Excalibur(){strength = 132;}
};

class Claymore : public Sword {
    public: Claymore(){strength = 98;}
};

Sword* playerSword = new Excalibur();

// you can easily change the sword that way
// anything you need a sword to do is put in the Sword class
// this is also easier to extend than a switch
// if you add a sword type in a switch, you have to modify ALL of the switch statements that use it
// with polymorphism, it is as easy as "new YourSwordNameHere()" and everything will automagically work

switch(swordType){
    case EXCALIBUR: /* do stuff */ break;
    case CLAYMORE:  /* do stuff */ break;
}

Share this post


Link to post
Share on other sites

If they're the best solution to your problem, use them.  Otherwise, use whatever is the best solution.

 

 

Switch statements are useful for many types of processing.  Compilers are quite good at optimizing them in the general case, although obviously there are times where information you know can lead to better solutions. Internally the can be implemented as jump tables, if/else trees, branching trees, and sometimes even binary searches depending on the number and distribution of case statement values, which the compiler can intelligently choose between based on analysis.  

 

Unless you are applying specific knowledge about the problem, such as knowing one option accounts for 80% of all the items and should be specially handled, switch statements can be a great way to go.

 

 

 

Nested switches can make sense if you have a bunch of processing based on data, and some sub-processing based on additional data to use them.

 

Consider:

switch(message.type) {
  case MSG_FOO:
    switch(message.subtype) {
      case FOO_TYPEA:
        ...
        break;
      case FOO_TYPEB:
        ... 
    }
 ...
}

There are other alternatives, of course. If you are dealing with code rather than data, inheritance and virtual functions are the typical route for this. And you can implement the methods the compiler uses like if/elseif/elseif trees, branching decision trees, and binary searches to function pointers yourself if you choose.

 

 

As for a program that intentionally uses all the levels of nesting the compiler allows, no, I've never seen a productive program attempt to do that.  I'm sure there are obfuscated code contests where people have taken advantage of it, and I'm sure code generation tools have occasionally bumped in to that limit.  In the ordinary case of things it is uncommon to reach compiler implementation limits.

Share this post


Link to post
Share on other sites

 

(sword example)


I wouldn't make derived classes just to change values. I would have a data table of different kinds of swords, without any separate classes.

 

 

Of course not. I was merely giving an object-oriented example using C++. Were I to give a more practical demonstration, it would use an unnecessary amount of screen space for what was being conveyed.

Share this post


Link to post
Share on other sites

Depends on your situation.

If you need a second switch, then you probably should review CAREFULY your design. 

However, I recommend using switch cases for constant values that you probably expect. And a second nesting if you really need to operate on inner type (which is also constant and won't be change! Like... int 16/32/64 etc...).

 

In OOP design I do fully recommend avoiding switch statements. They are a pain in the ass when managing states. 

When upgrading your code or adding a new feature, these statements easily become bottlenecks in the design since you need to add a new switch case and then look how to add the new method or function. When there is a big variety of objects. It gets even harder.

 

The sword example which was given by Shyr is probably the most amatuer way to program. and I did so when I just started.

The derived class is not a good example either because any unique sword has different data but same behavior. So you don't really need another class. Just reuse the base sword class.

The point of it, is to explit as much as you can the reuse of code, and avoid any big switch statmens. 

Share this post


Link to post
Share on other sites

The only time I recall having good reason to use a nested switch statement is in handling user input, where it's common practice to make pretty liberal use of them. This usually looks similar to the example prevented by frob, and is done for the same reason he described. Of course there are ways to prevent them in even that case, but it usually serves no purpose to do so.

Share this post


Link to post
Share on other sites

Use your own judgement and try to strike a balance between readability and logical structure.  Sometimes it just makes more sense to nest things, since whatever you're doing isn't really important enough to warrant its own function, and it makes more sense for it to be part of the current scope.  Other times, it hurts readability.  Don't blindly listen to know-it-alls who claim you should always do things one way or another way (except for me when I say you should ALWAYS COMMENT YOUR CODE   :))

Share this post


Link to post
Share on other sites
Here's one from the innards of my virtual machine for handling math operations (+, -, * and /) on an exploding number of combinations of different types. I don't think there is anything wrong with this personally, others might disagree.
 
template<class T> bool operation(State &state, const TypedValue &a, const TypedValue &b, TypedValue &c, Om::Value &res)
{
    switch(a.userType())
    {
        case Om::Type::Int:
        {
            switch(b.userType())
            {
                case Om::Type::Int: return op<T, int>(state, Om::Type::Int, a, b, c, res);
                case Om::Type::Float: return op<T, float>(state, Om::Type::Float, a, b, c, res);

                default: break;
            }

            break;
        }

        case Om::Type::Float:
        {
            switch(b.userType())
            {
                case Om::Type::Int: return op<T, float>(state, Om::Type::Float, a, b, c, res);
                case Om::Type::Float: return op<T, float>(state, Om::Type::Float, a, b, c, res);

                default: break;
            }

            break;
        }

        case Om::Type::String:
        {
            if(T::type == Addition && b.realType() == Om::Type::String)
            {
                c = TypedValue(Om::Type::String, state.allocate<StringEntity>(state.entity<StringEntity>(a.toUint()).text + state.entity<StringEntity>(b.toUint()).text));
                return true;
            }

            break;
        }

        default: break;
    }

    res = Om::ValueProxy::makeError(state, error(a.userType(), b.userType()));
    return false;
}
It is templated on structures like Add, Sub etc, so the same method can be used for all math operations via compile-time polymorphism.

Share this post


Link to post
Share on other sites

 


I understand; although a newcomer won't! We have to be careful with what our demonstrations and examples teach to new programmers. In most of the derived class examples I see that are designed to show an idea to new programmers, it's REALLY often a case where an experienced programmer would say "Nooooo~, an experienced programmer would never do this!". The problem is that the newcomer will see the example and believe that it should be done that way. They don't yet know what good code vs. bad code looks like yet, and they don't know what all of the available options are for solving different problems.

I think that examples are ideal if they can be made so they are both understandable by new programmers and something that an experienced programmer would not object to. We can improve the entire programming community if we're careful with our examples to avoid misunderstanding.

 

 

When I was learning how to code I truly appreciated simpler examples as opposed to extremely complex ones that made it even more difficult for me to learn. Examples that cannot be understood by the one being taught are pointless because they cannot be absorbed by someone who has no clue what it means. And the purpose of an example is not to provide production code that will satisfy an "experienced programmer", but to illustrate an idea that will help the student understand a concept. Knowing how to code and knowing how to teach others are two very different concepts.

 

When I was first learning C++, an "experienced programmer" showed us his 300+ lines of production code as an example, asked us to modify them however we liked after a lengthy explanation of what could only sound like a foreign language to those who didn't know C++ at the time, and he became frustrated because we had no idea what to do or what anything did. I now understand that he was showing us a class and pointer logic. That could have been explained in about 10 lines of code. It would have saved us a lot of headache in the start, and maybe we would have been able to actually understand his code if we knew what the underlying concept was. Please remember that everyone has to start somewhere, and that somewhere should not be dictated by the teacher but by the student's current knowledge.

 

In any case, the original poster asked if he/she should use nested switch, and the answer to that question is no. Yes, you can. But just because you can doesn't mean you should or that it's a good practice. C++ allows the use of "goto" statements, but it is not recommended that you use it just because it's there.

 

This is because nested switch adds unnecessary complexity to a program and is difficult to maintain. When you write a switch statement, you must take into account the maintainability of the code. It will lead to much code duplication of the cases. Should you need to update your switch with say a new case, you would then have to go back to find and change every single place where you had that switch.

 

"Messy and difficult to read" code is certainly not clean code and should be avoided. If the OP is saying that his/her code is difficult to read, then we should suggest refactoring rather than ignoring this and justifying reasons to nest switch (make it even messier :) ). Code that is not clean is hard to maintain, and harder for team members who don't understand what your original intentions were (should you work with a team). If you find yourself using multiple switch statements, then you should refactor. If you are using C++ and interested in OOP as a whole, I recommend searching "design patterns" for alternative solutions as well.

 

Edit:

fixed typo

Edited by Shyr

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this