Previous | Table of Contents | Next |
One of the ideals of an object-oriented system is that each object be as independent as possible. I should be able to send a generalized signal to an object and have it respond appropriately. I shouldnt have to know how it carries out the response or even what precise type the object has.
To use another hardware analogy, I should be able to send out a general print command without knowing the make and model of the printer ahead of time. As long as everyone observes the same protocol for communication, my print command should always work. I know in an general way what the command should do: print my document. A dot-matrix printer would respond to the print command in a way quite different from the way a laser printer would respond.
The point is that with object orientation, I want my main function or main loop to know as little as possible. Decisions as to how to carry out a command should reside in the objects themselves. There are times when it is much better for the main function to send only a general signal.
In Chapter 9, Virtual Functions and Why Theyre Good, I show a simple example of how this might work. The example uses a series of menu commands. When a user selects a command, the main function sends a message, Do_Command, to the appropriate menu object, which then responds in a completely different way depending on which command it represents.
Figure 1.4 A polymorphic menu system.
This approach is superior because the program is never limited to a particular set of commands. All the main function does is to send a Do_Command message to the appropriate menu object, and then the right thing happens. This approach supports future development of new menu commands without the need to rewrite the main function. It also opens up the possibility of dynamically changing menu items. The menu can grow or change at run time, which is impossible with a traditional approach.
Object-oriented theorists refer to this mechanism as polymorphism (many forms, from the Greek). This is a long, ugly word, but all it really means is decentralized control. Different objects can respond to the same message (function call) in different ways. Fewer decisions have to be centralized in the main loop.
This kind of design, by the way, is a critical part of any graphical user interface system, such as the Macintosh or Microsoft Windows. The operating system cannot know ahead of time how all windows might work; otherwise, application development would be extremely limited. Instead, the operating system must rely on the individual windows to respond in their own ways to general messages (such as Initialize your display).
This same kind of mechanism can be implemented in C by using callback functions. But callback functions are wild and unregulated. To control how objects respond to messages, C++ uses a more structured approach involving inheritance hierarchies and virtual functions. You'll learn more about these terms in Chapters 8 and 9. In practical terms, the important thing to know is that the C++ approach is more reliable, convenient, and self-documenting.
Object orientation is what's most interesting about C++. But perhaps the most fundamental theme in C++ is that it has a much stronger concept of types than C does. Even object orientation can be seen as an extension of the idea of stronger types: C++ strengthens types to the extent that they can contain code as well as data.
C++s emphasis on types is an aid to safer, more reliable programming, whether or not you use objects. In C, it is easy to accidentally use a variable declared as a four-byte floating-point number in one module and as an eight-byte number in another module. The result is a large error at run time. C++ prevents such errors through type-safe linkage, which prevents the linker from equating two symbols having different type information.
Another concept that runs through C++ is overloading, which uses type information to distinguish how functions and operators should work. Overloading reflects the importance of types in the language.
Overloading means reusing a name. In C++, one of the most common examples of this is function overloading, which means writing two different versions of the same function. In actual fact, overloaded functions are completely distinct and have separate function definitions as well as separate declarations. But from the programmer's perspective, function overloading is a convenient way of providing multiple variations on the same theme.
For example, you can write two different versions of the GetNum function. One version takes a pointer to an integer, and the other version takes a pointer to a floating-point number. Each version has a separate function definition. C++ uses the type information in the argument list to differentiate the functions. When a call to GetNum appears in source code, C++ looks at the data type of the argument to determine which of these two functions to call.
void GetNum (int *pn); void GetNum (double *pf);
Operator overloading is similar to function overloading. When C++ encounters an operator (such as +, -, /, or *), it examines the types of the operands to determine how to evaluate them. Implicit in this last statement is the idea that operators can be applied to any type. And this is basically true. In C++, you can apply addition (+), subtraction (-), or any of the other standard operators to a type-provided that the type declaration defines how the operator works. In other words, you can write a function that defines how the addition operator (+) works when applied to your type.
For example, in Chapters 5, 6, and 7, I build a CStr class that uses addition (+) to concatenate strings, just as in Visual Basic.
CStr name, first (Bernie), last (Schwartz); name = first + last;
Operator overloading, in effect, enables you to create types that look like true extensions to the C++ language. These types are as fundamental as int (integer) or long (long integer).
Previous | Table of Contents | Next |