Features


Using OOP Techniques Instead of switch in C++

Michael Wiebel and Steve Halladay


Michael Wiebel is a computer scientist applying object oriented programming techniques to the study of advanced architectures for StorageTek. He has written articles on advanced programming issues for several technical publications. Michael has a B.S. in computer science from Colorado State University. He can be reached at (303) 673-7082.

Steve Halladay currently works for StorageTek in Louisville, CO. He has over 10 years experience developing products like CSIM and Multi-C in C and C++. Steve received his MS in Computer Science in 1982 from Brigham Young University. You can reach Steve at Storage Tek (303) 673-6683.

In 1968 Edsger W. Dijkstra ushered in the structured-programming era with his letter to the editor detailing the dangers of the goto statement. Now C++ programmers need to beware of a statement they have grown accustomed to using in C — the switch statement. Usually, the switch statement in an object-oriented program causes the program to be more susceptible to bugs and less maintainable. C++ programmers should avoid the switch statement and learn how to apply inheritance and dynamic binding to make their programs more flexible, maintainable, and reliable. In this article, we will discuss the failings of the switch statement when used in C++ and we will present the concepts a C++ programmer needs to exploit the full power of C++.

Sample Code

Most C programmers are familiar with the kind of code presented in Listing 1. Listing 1 is the header file for a new game program. The game currently has four types of playing pieces encapsulated by the enumerated type GAME_PIECE: pawns, bishops, rooks, and kings. The variable piece_type (defined to be of type GAME_PIECE) in the struct PIECE_STRUCT is a discriminating record. C Programmers typically use discriminating records to identify the enumerated type value represented by a struct. In our program, piece_type will identify the kind of game piece represented by a variable of type PIECE.

Listing 2 contains a portion of C code that performs a move operation based on the type of game piece identified by the discriminating record piece_type. The switch statement in the function piece_move determines what kind of game piece the calling function passes and calls the appropriate move routine. Many functions that perform operations based on the value of the discriminating record may be scattered throughout the code of a large program.

Cost of Maintaining Code

Maintaining and extending programs like this one can be extremely costly and painful. For proof of this, consider extending the game program to include a new playing piece called queen. The identifier QUEEN must be added to the enumerated type GAME_PIECE. A new struct must be added to the union MOVE_U. The program needs a function that moves the queen playing piece. But the costliest changes are those to the existing code. Each switch statement that operates on the discriminating record must be located and modified to include the new game piece.

Making changes to existing (and presumably working) code is like opening a door and letting the bugs crawl in. Bugs introduced in this way are expensive to find because isolating the defective code will take time. If we only add code to the program then the bugs must be in the new code. Since changes are made in the old code the scope of the search must include all code modules added or changed. In a large program requiring the modification of many functions across several files debugging is excruciatingly painful.

Advantages of OOP

Object-oriented programming (OOP) makes the tasks of maintaining and extending the functionality of programs easier. OOP languages support the concepts of data abstraction or encapsulation, inheritance, and polymorphism. The resulting code is more flexible and easier to maintain.

Abstraction lets OOP programmer's encapsulate data and operations to form abstract data types, or types. The keyword class in C++ supports this concept. Listing 3 shows one possible application of abstraction in the game program. This listing replaces the struct PIECE_STRUCT with a class piece and uses the keyword private to hide the data from other classes. Creating a game-piece object is as simple as declaring a variable of type PIECE_CLASS such as:

// an object is an instance of a class.
PIECE_CLASS     pawn;
What have we gained by taking advantage of encapsulation? Restricting access to the data limits the scope of a debugging session to only those functions that have access to the data in error. Because the discriminating record still exists we have not eliminated the costs of modifying and maintaining the program. The first step to removing the cumbersome discriminating record is to apply the principles of inheritance.

Inheritance

Inheritance is the concept that allows OOP programmers to reuse and extend existing (and presumably bug-free) classes. Thus inheritance improves the reliability of programs and reduces program development time. C++ programmers inherit classes by placing a colon after the class name followed by the name of the class they wish to inherit. In the following example, class B inherits from a class named A:

class B : A
{
    /* class B declaration body */
};
Class B is a derived class because it inherits from the class A. Class A is a base class. Programmers create base classes when they notice that types have features in common. One way of locating base classes is to explore kinds of relationships. In our game program, pawns, bishops, rooks, and kings are all kinds of game pieces. Therefore we should create a base class (game_piece) and derive each individual game piece from this base class.
Listing 4 shows the new game program header file.

The discriminating record is no longer included in any of the class declarations. Objects are declared to be of a specific type of game piece. Each derived class inherits the public member functions and public member variables of the class game_piece. Derived classes cannot access base class private data directly; they must use the public interfaces. To create a king game piece declare a variable of type king such as

    king player1_king;
Listing 4 uses the keyword virtual before the base class functions position and move. This signals the compiler that the function may be declared in a derived class. Virtual functions are commonly used in defining abstract base classes — classes that do not provide implementations for member functions but do provide a common interface for a group of classes.

Polymorphism

Each derived class has its own implementation of the functions move and position. The pawn class move function may only move the pawn game piece one space vertically, while the bishop class move function may only move the bishop game piece diagonally. Different implementations of functions with the same name is an example of polymorphism. The code fragment

    // move a pawn
    plyr1_pawn.move ();
    // move a bishop
    plyr2_bishop.move();
will first move the object plyr1_pawn using the implementation of move declared in the class pawn. The object plyr2_bishop moves using the implementation of the move function declared in the class bishop.

Dynamic Binding

The previous code fragment is also an example of static binding. It is static because the compiler can determine the type of object and the virtual function to call at compile time. When the compiler cannot determine the type of object at compile time, the code exhibits dynamic binding. To use dynamic binding in C++, refer to an object of a derived class by using a pointer to the base class. In the game program a pointer to the base class would be a pointer to game_piece:

    game_piece *gamep;
From a code fragment such as

    gamep->move ();
it is not possible to determine the type of object until runtime.

Rewriting the function piece_move to take advantage of polymorphism and dynamic binding yields a one line function

    void piece_move (game_piece *gp)
    {
     gp->move ();
    }
Ideally, the derived classes and the code implementing their member functions will be in separate .h and .cpp files. To add a new piece called queen all we have to do is create a new header file specifying the queen class (that we can derive from game_piece) and create a new .cpp file with the member function implementation code. We do not have to modify existing code and risk the possibility of introducing bugs into the program. There are no switch statements to hunt down and modify. We have improved the maintainability, extensibility, and reliability of the game program by applying the principles of object-oriented programming and C++.

C++ is a powerful language. It combines the power of C with the promise of object-oriented programming. The principles of OOP help programmers create programs that are more reliable, maintainable, and extendable than programs developed using other methods. C++ is not an easy language to master; neither is C. But the benefits of C++ and object-oriented programming make it worth the effort.

Sidebar: When to Use the switch Statement