The object-oriented paradigm has three defining characteristics: encapsulation, inheritance, and polymorphism. Encapsulation and inheritance can stand independently, but polymorphism requires inheritance. Although polymorphism is a potent tool, it isn't easy to fully appreciate its power outside large, complex programs. Therefore, this semester focuses on its syntax and behavior while forgoing more useful examples. Rest assured that subsequent courses will build on this basic foundation.
Chapter 12 covers several topics, but they all lead up to and revolve around a central theme: polymorphism. Stated without detail, polymorphism is just a way of choosing which function to execute when multiple functions match a function call. Remarkably, it permits the program to delay choosing a function until runtime - until the moment of the call itself! You ask yourself, "Why wouldn't I know which function to call?" Let's set the stage by posing a problem and outlining a solution that doesn't use polymorphism. Later, we'll revisit the problem and see how a polymorphic solution can simplify the program.
Drawing Shapes Without Polymorphism
Our proposed program must perform three tasks:
Specify a number of classes that represent different, drawable shapes.
Prompt the user to choose between one of the shapes and instantiate an object to represent the choice.
Draw that shape many times from many locations within the program.
The three parts of our program might look something like this:
class Circle
{
public:
void draw();
};
class Rectangle
{
public:
void draw();
};
class Triangle
{
public:
void draw();
};
Specifying shape classes.
These classes are representative of the typical classes appearing in a program. They are the same in both the polymorphic and the non-polymorphic examples.
Figure 3 partially answers the question, "Why wouldn't I know which function to call?" The programmer doesn't know which function to call until the user selects a shape, which only happens after the program starts running. So, the program must be sufficiently dynamic to select the correct draw function from several choices while running. Our current program does this but with more variables than necessary. Furthermore, imagine that the user wishes to add another shape to the program: an Ellipse. What code must we modify?
Function Binding, Part 1
When the compiler processes a function, it generates the machine code from the function body, which the operating system loads into memory with the rest of the program when it runs. Normally, the compiler binds a function call to the function's machine code by generating instructions to jump to the code's memory location. The program executes the machine code before returning to the calling point. So, for the function calls
c->draw();
r->draw();
t->draw();
appearing in Figure 3, the compiler can bind statement 1 to the Circledraw function, statement 2 to Rectangle, and statement 3 to Triangle. Programmers use three terms to name and describe this kind of binding:
compile-time binding
static binding
early binding
The terms are synonyms, meaning the compiler can bind a function call to a specific set of machine instructions when it compiles the program. Compile time-binding works most of the time - all the functions we have written this semester are processed this way. But, as Figure 3 demonstrates, compile-time binding doesn't always work and is sometimes inconvenient and awkward. Polymorphism allows us to write more elegant code that is easier to maintain.
Polymorphism Requirements
Polymorphism is active by default in Java programs, and programmers must explicitly deactivate it when it's not wanted. Conversely, before programmers can use polymorphism in a C++ program, they must activate it by satisfying five requirements. We have previously explored some of those requirements, but others are new, and we'll explore them in greater detail in subsequent sections.
Requirements For Polymorphism
Inheritance
Function overriding
A pointer or reference variable (polymorphism cannot operate through an automatic or stack variable)
Upcasting
A virtual function
These required features roughly outline the remainder of the chapter.
Overloading and Overriding Functions
The terms overloading and overriding are similar and therefore easily confused. Let's take a moment and review their similarities and their differences. You might find it worthwhile to review Figure 2 where the terms were first introduced.
Overloaded Functions
Overridden Functions
Are defined in the same class
Must have unique argument lists
May have different return types
Are defined in two classes related by inheritance
Must have the same name
Must have exactly the same argument list
Must have the same return type
Although polymorphism requires a function override, programs may override functions without making them polymorphic.