- Overview
- Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
- Tips and Techniques
- Using the Swap() Algorithm
- Using class stopwatch for Performance Measurements
- Extending <iostream> to Support User-Defined Types
- Using auto_ptr To Automate Memory Management
- Using auto_ptr To Automate Memory Management, Part II
- Using auto_ptr To Automate Memory Management, Part III
- Using enums as Mnemonic Indexes
- Create Objects on Pre-Allocated Memory Using Placement-new
- Online Books: Placement-new
- Bitwise Operators
- Bitwise Operators II
- Who's this?
- A Reference Guide
- The Virtues of Multiple Inheritance
- Interfaces
- Multiple Inheritance: Construction and Destruction Order
- nothrow new
- POD Initialization
- Object Initialization
- const Declarations
- The Semantics of volatile
- inline Functions
- Project Organization Guidelines
- All About bool
- typedef Declarations
- State of the union
- Dynamic Cast Uses
- Integrating C and C++
- const-Correctness
- const-Correctness: Advanced Issues
- Sprucing Up Legacy Code
- Virtual Constructors
- Naming Names
- Function Calls
- Speaking Standardese (updated)
- Speaking Standardese: the One Definition Rule
- Declarations and Definitions
- More on Declarations and Definitions
- The Most Vexing Parse
- Finally, At Last
- Sound Bytes (Admittedly Off Topic)
- Local Classes
- Complex Arithmetic
- Floating Point Woes
- String Manipulation
- The Object Model
- The Object Model II
- The Object Model III
- Temporary Objects
- Temporary Objects: Advanced Techniques
- Over-Engineering
- Security Enhancements
- Drop the (automatic) Pilot
- Choosing the Right Container
- Choosing the Right Container II
- Choosing the Right Container, Part III
- Arrays and Pointers
- Low-Level File I/O
- Low-Level File I/O Part II
- static Declarations, Part I
- static Declarations, Part II
staticInitialization Order- Revisiting the Deprecation of File-Scope Static
- Virtual Memory and Memory Mapping
- Cellular Phone Programming Guidelines
- The Handle/Body Idiom
- Whole Program Optimization, Part I
- Whole Program Optimization, Part II
- Manipulating Directories
- Window Dressing
friendDeclarationsfriendPart II: the Interaction of Friendship and Template Classes- Forcing Object Allocation on Specific Storage Types
- Lazy Evaluation
- Cache and Carry
- Controlling a Container’s Capacity
- Non-Blocking I/O, Part I
- Non-Blocking I/O, Part II
- Using Unions for Automatic Conversion
- Launching a Child Process
- switch Statements
- Introducing the "struct Hack"
- Scoped Enumerators
- Doing Statistics with STL
- Fixing the "Unresolved External" Linkage Error
- Understanding Calling Conventions
- Understanding the Empty Base Optimization
- Implementing RPC with the door Library, Part 1
- Implementing RPC with the door Library, Part 2
- Eliminating Two Common Pointer and sizeof Bugs
- Command Line Arguments
- Performance Myths Busting
- Tag Names And Types Part I
- Tag Names And Types Part II
- The Infamous goto
- Trimming Strings
- Can Objects Live Forever? Part I
- Can Objects Live Forever? Part II
- Five Ways to Improve Your Functions
- Member Aggregate Initialization
- Five Futile Coding-Style Debates
- The Good Parasite Idiom: An Exercise in OOD
- The Good Parasite Idiom: An Exercise in OOD, Part II
- The Good Parasite Idiom: An Exercise in OOD, Part III
- Ten Techniques to Reduce the Size of Your Classes, Part I
- Ten Techniques to Reduce the Size of Your Classes, Part II: Inheritance Issues
- Ten Techniques to Reduce the Size of Your Classes, Part III
- Ten Techniques to Reduce the Size of Your Classes IV
- Taking the Address of an Object with an Overloaded Operator &
- strcpy() -- How and Why Does It "Just Work"?
- Anonymous Structs
- Five Easy Ways to Reduce The Size of your Executables
- Standard Layout Classes and Trivially Copyable Types, Part I
- Standard Layout Classes and Trivially Copyable Types, Part II
- Five Simple Code Sanity Checks
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
switch Statements
Last updated Apr 28, 2006.
switch statements are seemingly a trivial construct that every C++ programmer learns right from the start. However, a deeper glance reveals several tricky issues that could lead to nasty bugs and misunderstandings. In fact, switch statements are among the most error-prone features of C++. However, a few rules of thumb will help you avoid all these bugs.
The Basics
A typical switch statement contains a switch condition in parentheses and a list of one or more case statements. For example:
switch (event)
{
case MOUSE_MOVE:
//do mouse move;
break;
case RIGHT_CLICK:
//..
}
Even this tiny example has several potential problems.
A switch condition must have an integral type. This means that you can’t use a switch clause to test strings, pointers or even floating point values. event can therefore be of type int, enum , bool etc, but not float, or void *.
Each case block is associated with a constant integral expression that functions as the case’s label. The label therefore shall not be a variable, or a constant expression that isn’t convertible to int.
Ideally, a case label should have a meaningful name instead of a hardcoded integral value. Enumerated types are a perfect match for labels as they enable the compiler to detect subtle bugs such as the accidental omission of certain cases. Alternatively, constants and even macros (since you insist!) should be used instead of bare numeric values.
Although in certain conditions numeric values used as label make sense, you should avoid them. Here’s why. A colleague of mine once wrote a switch statement that looked like this:
switch (value)
{
case 000:
//do zero;
break;
case 010:
//..
break;
case 100:
//..
break;
default:
cerr<<"wrong option!";
}
She was puzzled by the outwardly erratic behavior of this code. Can you see what her bug was? Remember: in C and C++, every literal number preceded by 0 is an octal number. My colleague wanted to ensure that all case labels were neatly formatted so she padded them with leading zeroes, not knowing that these leading zeroes caused the labels to be evaluated as octal numbers! Thus, the second label was identical to:
case 8:
This bug would have been avoided had she used a symbolic constant instead:
switch (value)
{
case ZERO:
//do zero;
break;
case TEN:
//..
break;
case HUNDRED:
//..
break;
default:
cerr<<"wrong option!";
}
The default case
The most common bug associated with switch statements is forgetting to include a default: label. As a rule, always add a default: case at the end of your switch statement (default is a reserved keyword) for trapping illegal values or grouping valid values for which a uniform treatment is required:
switch (event)
{
case MOUSE_MOVE:
//do mouse move;
break;
case RIGHT_CLICK:
//..
default:
cerr<<"unhandled event";
}
Even if you’re 100% sure that the default case is unnecessary, do provide it. Otherwise, your colleagues as well as many automated source code analyzers will balk at its lack.
Falling from Case
In one of their interviews, Brian Kernighan and Dennis Ritchie crowned "falling of the end" of a case statement as their worst design mistake in C. Let’s see what they meant.
case MOUSE_MOVE: move_mouse(); case RIGHT_CLICK: do_right_click(); case DOUBLE_CLICK: do_double_click();
When the switch condition evaluates as MOUSE_MOVE, the matching case block executes, calling move_mouse(). This is exactly what you expect under the circumstances. However, after calling move_mouse(), the execution flow continues from the same location. That is, the program advances to the next case label, executing do_right_click(). Guess what happens next? The DOUBLE_CLICK case block is executed as well, causing do_double_click() to be called. Why is this?
Historically, switch blocks were designed as a goto statement with plenty of syntactic sugar. Therefore, when control is transferred to a label, the execution flow continues from that point until another break statement interrupts the current flow. Programmers who’ve never used goto in their lives find it very confusing; they tend to think of each case as the equivalent of a function call. When the code for a specific case has executed, they expect that control should exit the switch statement, which of course doesn’t happen.
To overcome this idiosyncratic behavior of switch cases, you must provide an explicit break statement at the end of each case label.
case MOUSE_MOVE: move_mouse(); break; case RIGHT_CLICK: do_right_click(); break; case DOUBLE_CLICK: do_double_click(); break;
Scope
Because a switch statement is essentially a collection of goto statements with syntactic sugar, they have another counter-intuitive property with respect to the scope of locally-declared objects. Many programmers mistakenly assume that an object declared inside a case block is local to that block. This is incorrect, though. In the following code, the object up is declared in the second case block. However, it’s visible from every other case block. In other words, the scope of up is the entire switch, not just the case in which it’s declared:
case DOWN: up.show(); //undefined behavior, up is visible but not yet initialized break; case UP: Widget up; up.show(); break;
This property could lead to a serious bug: whereas the scope of the object is the entire switch block, its initialization takes place only once the case in which it’s declared is executed. Therefore, the first case block may access the object up, although the results are undefined since up hasn’t been constructed yet. Every decent compiler nowadays issues a warning in this situation. Alas, many programmers don’t understand this warning, often ignoring it!
To restrict the scope of objects to a case block, always use braces:
case DOWN:
do_something(); //up is visible from here
break;
case UP:
{
Widget up; //inaccessible from other case blocks
up.show();
break;
}



