c++ should nested switch statements be avoided?

Started by
17 comments, last by Shyr 7 years, 12 months ago

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.

Advertisement
Just because one can, does not necessarily mean one should.

It looks quite messy and difficult to read

Therefore avoid it when you can reasonably do so. Depending on the situation there's a decent chance that the nested switches could be within function calls, which is easier to read and probably supports DRY in many cases.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Well, I can't think of any reason to use multiply-nested switch statements. I think the deepest nesting I run into frequently is two levels (and that's not in "good" code) and the worst I recall encountering is three levels (and that was in "horrific" code).

"Good" or "bad" practices aren't meaningful. If the code makes sense for the problem domain, use it. If it doesn't make sense, don't use it. You haven't presented a problem domain, but given my experience, I'd learn towards saying "nesting probably isn't good."

Note that there's a strong argument made against even having loops or complex conditionals in functions (and that these should generally be abstracted behind other functions or variables). Switch statements would be no different in that school of thought.

Sean Middleditch – Game Systems Engineer – Join my team!

But isn't it better to just put methods in one switch, that have other switches in them? It's way easier to modify code like that.

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;
}

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.

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

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

While you're nesting those 256 switch statements, you might as well add a few gotos.

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;
}

This topic is closed to new replies.

Advertisement