12.4. Actor 5: A Polymorphism Example

Time: 00:12:19 | Download: Large Small | Streaming

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.

Basic Polymorphism

The chapter began with a list of five elements that programs must provide to enable polymorphism:

  1. Inheritance
  2. Function overriding
  3. A pointer or reference variable (polymorphism cannot operate through an automatic or local variable)
  4. Upcasting
  5. A virtual function
As a practicing computer scientist, no one will ask you to list these elements. However, reading and understanding the behavior of a program is an expected skill. In the case of polymorphism, you must recognize the presence or absence in a program of the five elements. As you study the following changes to the Actor program, compare the changes to the list of requirements. A program must implement all five features to activate 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;
}
Preparing for polymorphism. The main function illustrated here provides the next two key requirements for polymorphism:

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
}
Enabling polymorphism. Making the display function 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
Polymorphic behavior. The call 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.

An Extended Polymorphism Example

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.

A picture of a circularly linked list formed by lining nodes together with pointers. Each list node, represented by a rectangle, is an object instantiated on the heap with <kbd>new</kbd>. Nodes contain data and a pointer to the next node in the list. A special node, called a list head or header, doesn't contain data but forms the interface between a program and the list. The list is circularly linked because the pointer in the last node points back to the list head.
A circularly linked list. The list head does not store any data. It is used as a "handle" to manipulate or "hang on to" the list. The "data" in this illustration is a pointer to an instance of the Person class.

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()
Default Constructor builds a new, empty circularly linked list
~CList()
Destructor deallocates all of the list nodes. The stored data may also be deleted by removing a comment in the destructor function body
Person* insert(Person* p)
Creates a new list node, stores p in the new node, links the node into the list at the position determined alphabetically by the name of the inserted person, and returns p as a convenience. If there is already a node in the list that stores a person with the same name as is stored in p, then the list is unchanged and a pointer to the existing node is returned. The searching operation requires that the Person class define a getter function: string getName() that returns the person's name
Person* search(Person* p)
Searches the list for a person with the same name as in p; returns a pointer to the stored person is a match is found or returns nullptr if no match is found
Person* remove(Person* p)
Searches the list for a person with the same name as in p; removes the corresponding node from the list if a match is found and returns the removed node. Returns nullptr if no match is found
void list();
Lists out or prints all of the instances of the Person class stored in the list by calling the display function for each Person in the list

Downloadable Code

CList member functions.

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;
}
A getter function for class Person.

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;
}
Inserting data (instances of Person and its subclasses) into a list.
  1. includes the CList header file so that the source code file "knows about" a CList.
  2. creates and names the CList object; I named the CList people in this example.
  3. creates new Person objects and inserts them into the list. Note that when the program instantiates Star and Actor objects, the insertion operation upcasts them to a Person.
  4. prints all of the information for each object in the list. The list function "walks" the list from the beginning to the, calling each object's display function. The objects in the list may not be in the same order as they were inserted because the list orders them alphabetically by name. But polymorphism calls the correct display function for each object, which prints the full and correct information.

Downloadable Code