10.2.3. Inheritance Example: Actor 1

Time: 00:04:20 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)
Review

The Actor family of example programs demonstrates various aspects of object-oriented programs. As such, they don't solve a problem beyond illustrating the syntax needed to build multi-class programs and use the connecting relationships. To gain the intended benefits from the Actor examples, you must focus on the illustrated syntax and study it for understanding. Your goal is to understand the concepts and syntax well enough that you can generalize them to solve new problems. We need a certain "critical mass" of code to demonstrate inheritance. So, the first version specifies three classes and a main function in a single file, and subsequent versions become increasingly complex.

UML Class Diagram Abstract Representation
A UML inheritance hierarchy with three classes: Person, Actor, and Star. Person is at the top without a superclass. Actor is in the middle, with Person as its superclass and Star as its subclass. Star is at the bottom, with Actor as its superclass. Person has a private string member variable named name, Actor has a private string member variable named agent, and Star has a private member variable named balance. The Person constructor takes one string argument, n; the Actor constructor takes two string arguments, n and a; and the Star constructor takes three arguments; the first two are strings, n and a, and the third is a double, b. All three classes have a void display function that does not have any arguments. Three nested objects in memory represented by three squares. An instance of Person is nested inside an instance of Actor, which is nested inside an instance of Star.
A three-class inheritance example. An inheritance hierarchy can be arbitrarily tall and wide. If we understand the inheritance concepts and syntax, we can generalize the minimal Actor example to create taller and wider hierarchies. The hollow arrowhead of the UML inheritance symbol attaches to the superclass, while the plain (undecorated end) attaches to the subclass. The Actor class demonstrates a class that is simultaneously a subclass and a superclass.

Recalling that inheritance is an is-a relationship, we can say that "a Star is-an Actor" and "an Actor is-a Person." Together, these relationships imply that "a Star is-a Person." The order in which the compiler "sees" the class specifications is significant - it reads and processes each source code or .cpp file once, from top to bottom. When a program instantiates a Star object, it allocates memory to hold it, which implies that the compiler must know the size of a Star object, including the embedded Actor and Person objects, at compile time. Another way of thinking about the challenge presented by the class specification order is whenever the compiler encounters an identifier, such as a class name, it must be previously declared and entered into the symbol table.

The classes in this example don't solve an algorithmically complex or interesting problem. Instead, information is "pushed up" through the inheritance hierarchy by chaining constructor calls, and information is "pulled down" through the hierarchy by chaining calls to the overridden display function.

Actor 1 Program Listing

The first version of the Actor program is presented in a single file, which the compiler reads once, from top to bottom. Notice that Star depends on Actor and Actor depends on Person, forcing programmers to specify the classes from the least derived (Person) to the most derived (Star). This organization is unrealistic, but it allows us to focus on creating and using inheritance. It also demonstrates the challenge of ordering the class specifications, segueing into the next example.

#include <iostream>
#include <string>
using namespace std;


class Person
{
    private:
        string    name;

    public:
        Person(string n) : name(n) {}

        void display() { cout << name << endl; }
};


class Actor : public Person
{
    private:
        string    agent;

    public:
        Actor(string n, string a) : Person(n), agent(a) {}

        void display()
        {
            Person::display();
            cout << agent << endl;
        }
};


class Star : public Actor
{
    private:
        double    balance;

    public:
        Star(string n, string a, double b) : Actor(n, a), balance(b) {}

        void display()
        {
            Actor::display();
            cout << balance << endl;
        }
};


int main()
{
    // Automatic variable/object
    Star s("John Wayne", "Cranston Snort", 50000000);
    s.display();

    // Dynamic variable/object
    Star* s2 = new Star("John Wayne", "Cranston Snort", 50000000);
    s2->display();

    return 0;
}
  • Builds the inheritance relationship.
  • Calls the superclass constructor (i.e., initializes the inheritance relationship). The highlighted list elements are function calls, so the number of arguments and their types must match the parameters in constructor functions.
  • Calls an overridden function (i.e., chains a subclass function to the overridden function in the superclass). You can generalize these statements to call any overridden member function.
  • Instantiates a Star object and calls the Star constructor, which starts the constructor call chain running from Star to Actor and from Actor to Person.
Actor1.cpp: multi-level inheritance. Data is pushed upwards in the inheritance hierarchy through a chain of constructor calls, and the data is pulled downwards through chains of display calls.