Two versions of Actor 5 demonstrate polymorphism. The first version begins with the Actor 3, and makes the display() function virtual
to demonstrate basic polymorphism. The second version is fleshed out, suggesting a purpose for polymorphism. Code fragments for each version are presented in the discussion, and links to the complete programs are given at the end of the section.
The completed Actor 3 example consists of five classes connected by three class relationships: inheritance, aggregation, and composition. The last two relationships don't participate in polymorphism, so the demonstration includes the composed and aggregated classes without modification. All the changes made here center around the three classes related by inheritance.
The chapter began with a list of five elements that programs must provide to enable polymorphism:
The Actor 3 example has three classes, Person, Actor, and Star, related by inheritance. Each class defines a display function that has a void
return type and an empty parameter list. So, the display function satisfies the requirements for function overriding. So, Actor 3 has the first two "ingredients" for polymorphism, but we must modify the example to get the last three.
#include "Person.h" #include "Actor.h" #include "Star.h" #include <iostream> #include <string> using namespace std; int main() { Person* s2 = new Star("John Wayne", "Cranston Snort", 50000000, "123 Palm Springs", "Ogden"); s2->setDate(1960, 12, 25); s2->display(); cout << endl; cout << *s2 << endl; return 0; }
The last step that we must take is to switch on polymorphism by making one of the functions in the Person class virtual
(requirement 5). For this example, we will make the display function virtual
(i.e., polymorphic) and contrast its behavior to that of the inserter function, which, because it is a non-member friend
function, cannot be polymorphic.
virtual void display() { cout << name << endl; addr.display(); // composition if (date != nullptr) // display if available date->display(); // aggregation }
virtual
, which satisfies requirement 5, is the final step to switch on polymorphism. Making display virtual
in Person also makes the display function in the two subclasses virtual
, but I like to add the keyword to the subclasses for clarity. When all five requirements are met, a function can exhibit polymorphic behavior.
John Wayne Ogden 123 Palm Springs Cranston Snort 5e+07 John Wayne Ogden 123 Palm Springs 1960 12 25
s->display();
runs the Star display function, despite s2 being a Person pointer, and produces the output highlighted in yellow. It is the function belonging to the instantiated object, not the pointer variable, that runs. Alternatively, the inserter function call, cout << *s << endl;
, causes the Person inserter to run, which prints the person's name, address, and date but does not print the actor's agent's name nor the star's bank balance.
The extended example relies on a dynamic data structure called a circularly linked list named CList. A data structure stores data (often objects), provides a set of (often standardized) operations that access the data, and organizes that data to optimize some operations. A linked list (see Figure 1) is a data structure that organizes the stored data linearly in a list that is formed or "held together" by pointers. A circularly linked list is a special kind of linked list where the pointer in the last data node points to the beginning of the list.
Although library-grade data structures can generally store any data type, the list used in this demonstration is limited for simplicity. It can only store instances of the Person class. There are many variations on linked lists, and this variation organizes the data alphabetically based on the person's name. This organization implies that the list can reorder the stored objects, so the storage order is not necessarily the same as the insertion order. We'll see that this organization makes a more interesting demonstration of polymorphism.
The CList source code may be difficult to understand at this point in your studies, but it will become quite clear after your data structures and algorithms class. Following standard object-oriented programming practices, we can use the CList class by understanding its public interface. Following common practice, we explore the CList public interface through a set of function prototypes:
CList()
~CList()
Person* insert(Person* p)
string getName()
that returns the person's namePerson* search(Person* p)
nullptr
if no match is foundPerson* remove(Person* p)
nullptr
if no match is foundvoid list();
Three of the CList functions (insert, search, and remove) require access to the name stored in each instance of Person. As the member variable storing the name is private, the Person class must define an appropriate getter function:
string getName() { return name; }
The getName function is the last part necessary to complete the extended polymorphism example. The following main creates a CList, adds three different kinds of Person objects to the list, and then prints all of the information for each object:
#include "Person.h" #include "Actor.h" #include "Star.h" #include "CList.h" // (a) #include <iostream> #include <string> using namespace std; int main() { CList people; // (b) Star* s = new Star("John Wayne", "Cranston Snort", 50000000, // (c) "123 Palm Springs", "Ogden"); s->setDate(1960, 12, 25); people.insert(s); Actor* a = new Actor("Dilbert", "Wally", "2401 Edvalson", "Ogden"); // (c) a->setDate(1961, 1, 1); people.insert(a); Person* p = new Person("Alice", "115 Elm", "Ogden"); // (c) p->setDate(1975, 5, 22); people.insert(p); people.list(); // (d) return 0; }