The following section builds extensively on previously introduced concepts and terms. Please review the following as needed:
Building a complete, working inheritance relationship requires two steps. First, programmers must establish the inheritance relationship between a pair of classes, and second, they must initialize the objects created by instantiating the subclass. Implementing inheritance takes place as a part of the subclass specification. Initialization means storing the first or initial values in the objects' member variables. If the initial value is a constant or otherwise known at compile-time, assign it to the member variable in the class specification. In more complex or dynamic situations, object initialization occurs through constructor calls. The Actor example at the right will provide a concrete context to illustrate these elements.
The notation for implementing inheritance consists of three parts:
The keywords "protected" and "private" can replace "public," but this is never done in practice because a whole-part relationship is more clear and better achieves the same results.
Inheritance is unidirectional, from the subclass to the superclass - the subclass "knows about" the superclass. Consequently, the syntax implementing inheritance appears in subclasses but not superclasses. In terms of the Actor example, only the Actor and Star classes require any inheritance syntax, highlighted in the following figure.
class Actor : public Person {...}
|
class Star : public Actor {...}
|
(a) | (b) |
The full class specification for a superclass must precede the specification for any subclass. Although uncommon, programmers can put all class specifications in the same file. In that case, the specifications must appear in UML inheritance hierarchy order - superclass at the top and subclasses below (the reverse direction of the inheritance arrows). The Actor 1 example demonstrates this ordering. More commonly, programmers put each class specification in a separate header file. In that case, they must #include the superclass header file above the subclass specification, appropriately "protected" by a #pragma or #ifndef. The Actor 2 example demonstrates this common practice.
In all but the most simple classes, initializing a newly constructed object is the task of a constructor function. When a program instantiates a subclass object, it simultaneously instantiates a superclass object, which is initialized or constructed by a constructor function. So, how do we call the superclass constructor when creating an instance of a subclass? There are two possible situations, each requiring a different answer to the original question.
The first and most simple case is where the superclass has a default constructor that appropriately initializes the superclass object for the inheritance relationship. In this case, the subclass constructor automatically and implicitly calls the superclass default constructor. Although this special case is simple, it does have two options.
Superclass Constructor | Subclass Constructor |
---|---|
class Alpha { private: ..... public: Alpha() { . . . } }; |
class Beta : public Alpha { private: int length; public: Beta() : Alpha(), length(20) {} // (a) Beta() : length(20) {} // (b) }; |
The second case deals with the more common and more general situation where the superclass either does not have a default constructor or another constructor is in some way better suited to the problem than the default. In this case, initializing inheritance requires explicitly calling a parameterized constructor. C++ does this through the subclass's initializer list. The notation creates a chain of constructor calls that pass some argument data from the subclass constructor to the superclass constructor.
Person Constructor |
Person(string n) : name(n) {} |
|
---|---|---|
Actor Constructor |
Actor(string n, string a) : Person(n), agent(a) {} |
|
Star Constructor |
Star(string n, string a, double b) : Actor(n, a), balance(b) {} |
Inheritance | Initializer List |
---|---|
class Actor : public Person |
Actor(string n, string a) : Person(n), agent(a) {} |
class
. On the other hand, initializer lists are always part of a constructor function. Furthermore, when an initializer list calls a superclass constructor, the call must be the first element in the list:: Actor(n, a), balance(b)
Constructors and setters are special functions used, at least in part, to initialize or update member variables. Naming the function parameters after the member variables they initialize is convenient. A standard naming convention adds the letter 'a' at the beginning of the member variable name to form the corresponding parameter name - the letter 'a' distinguishes the parameter (or argument) from the member variable with the same name when both are in the same scope. By chaining constructor calls, programs can pass data from subclass constructors to superclass constructors. If there is no scope conflict between the parameter and the member names, they may be the same.
Person Constructor |
Person(string a_name) : name(a_name) {} |
---|---|
Actor Constructor |
Actor(string name, string a_agent) : Person(name), agent(a_agent) {} |
Star Constructor |
Star(string name, string agent, double a_balance) : Actor(name, agent), balance(a_balance) {} |
Person(name)
, uses the parameter "name;" the second element, Person(string a_name)
, must use the parameter "a_agent."Classes and their constructors are specified downward from the top of the inheritance hierarchy (see Figure 1). However, constructors are called and run from the subclass upward in the hierarchy when the program instantiates a subclass. Each subclass constructor calls its superclass constructor. The call can pass some of its arguments to the superclass while retaining some for its class's member variables. The following figure illustrates the data passing sequence.