Hour 16. Extending Classes with Inheritance

What Is Inheritance?

It is a fundamental aspect of human intelligence to seek out, recognize, and create relationships among concepts. We build hierarchies, networks, and other interrelationships to explain and understand the ways in which things interact. C++ embodies this in inheritance hierarchies, making it possible for a class to inherit from another class.

Concepts can be categorized in many different ways. When you look at your dog, what do you see? A biologist sees a network of interacting organs, a physicist sees atoms and forces at work, a taxonomist sees a representative of the species canine domesticus, and a child sees a companion and protector.

Each category often can be divided further into subcategories. To a taxonomist, a dog is a canine. A canine is a kind of mammal. A mammal is a kind of animal and so forth. Taxonomists divide the world of living things into kingdom, phyla, class, order, family, genus, and species.

The taxonomist’s hierarchy establishes an “is a” relationship—a dog is a canine. There are “is a” relationships everywhere. A Toyota is a kind of car, which is a kind of vehicle. A sundae is a kind of dessert, which is a kind of food.

What is meant when something is described as a kind of something else? This means it is a specialization of that thing. That is, a car is a special kind of vehicle. Cars and buses are both vehicles. They are distinguished by their specific characteristics but share things in common with each other and other vehicles.

Inheritance and Derivation

These relationships are conveyed by inheritance. The concept of a dog inherits all the features of a mammal. Because it is a mammal, it moves and breathes air; by definition, all mammals move and breathe air. The concept of a dog adds to that definition the idea of barking, a wagging tail, and so forth. A dog has traits unique to dogs and traits common to all mammals.

Dogs can be divided further into hunting dogs and terriers; terriers can be divided into Yorkshire Terriers, Dandie Dinmont Terriers, and so forth.

A Yorkshire Terrier is a kind of terrier; therefore, it is a kind of dog; therefore, a kind of mammal; therefore, a kind of animal; and, therefore, a kind of living thing.

C++ attempts to represent these relationships by defining classes that derive from one another. Derivation is a way of expressing the “is a” relationship. You derive a new class, Dog, from the class Mammal. You don’t have to state explicitly that dogs move because they inherit that from Mammal. Because it derives from Mammal, Dog automatically moves.

A class that adds new functionality to an existing class is said to derive from that original class. The original class is said to be the new class’s base class.

If the Dog class derives from the Mammal class, Mammal is a base class of Dog. Derived classes are supersets of their base classes. Just as dogs add certain features to the idea of a mammal, the Dog class will add certain methods or data to the Mammal class.

A base class can have more than one derived class. Just as dogs, cats, and horses are all types of mammals, their classes would all derive from the Mammal class.

Animals and Inheritance

To facilitate the discussion of derivation and inheritance, this section focuses on the relationships among a number of classes representing animals. Imagine that you have been asked to design a children’s game—a simulation of a farm.

The game will have a whole set of farm animals, including horses, cows, dogs, cats, sheep, and so forth. You will create member functions for these classes so that they can act in the ways the child might expect, but for now you’ll stub out each method with a simple cout statement.

Stubbing out a function means to write only enough to show that the function was called, leaving the details for later. You do not have to fill in all the details as you work on the problem; the stubs act as placeholders.

The Syntax of Derivation

When you create a class that inherits from another class in C++, in the class declaration you put a colon after the class name and specify the access level of the class (public, protected, or private) and the class from which it derives.

Access control is covered later. For now, you use public, as in this example:

class Dog : public Mammal

This statement creates a derived class called Dog that inherits from the base class Mammal. The Mammal1 program in Listing 16.1 creates a full Dog class derived from Mammal.

Listing 16.1 The Full Text of Mammal1.cpp


 1: #include <iostream>
 2:
 3: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
 4:
 5: class Mammal
 6: {
 7: public:
 8:     // constructors
 9:     Mammal();
10:     ~Mammal();
11:
12:     // accessors
13:     int getAge() const;
14:     void setAge(int);
15:     int getWeight() const;
16:     void setWeight();
17:
18:     // other methods
19:     void speak();
20:     void sleep();
21:
22: protected:
23:     int age;
24:     int weight;
25: };
26:
27: class Dog : public Mammal
28: {
29: public:
30:     // constructors
31:     Dog();
32:     ~Dog();
33:
34:     // accessors
35:     BREED getBreed() const;
36:     void setBreed(BREED);
37:
38:     // other methods
39:     // wagTail();
40:     // begForFood();
41:
42: protected:
43:     BREED breed;
44: };
45:
46: int main()
47: {
48:     return 0;
49: }


This program has no output; it is only a set of class declarations without implementations. Nonetheless, there is much to see here and it will compile.

On lines 5–25, the Mammal class is declared. Because all mammals have an age and weight, these attributes are represented by the member variables age and weight in this class.

Six member functions are defined in the Mammal class: four accessor methods, speak(), and sleep().

The Dog class inherits from Mammal, as indicated on line 27. Every Dog object has three member variables: age, weight, and breed. Note that the class declaration for Dog does not include two of these variables, age and weight. Dog objects inherit these variables from the Mammal class along with all Mammal’s member functions except for the copy operator, the constructors, and the destructor.

Private Versus Protected

A new access keyword, protected, has been introduced on lines 22 and 42 of the Mammal2 program in Listing 16.1. Previously, class data had been declared private. However, private members are not available to derived classes. You could make age and weight public, but that is not desirable. You don’t want other classes accessing these data members directly.

What you want is to make the data visible to this class and its derived classes, which is accomplished by protected. Protected data members and functions are fully visible to derived classes, but are otherwise private.

There are three access specifiers: public, protected, and private. If a function has an instance of a class, it can access all the public member data and functions of that class. The member functions of a class, however, can access all the private data members and functions of any class from which they derive.

Therefore, the function Dog::wagTail() can access the private data breed and can access the protected data in the Mammal class.

Even if other classes are layered between Mammal and Dog (for example, DomesticAnimals), the Dog class will still be able to access the protected members of Mammal, assuming that these other classes have public inheritance.

The Mammal2 program in Listing 16.2 demonstrates how to create objects of type Dog and access its data and member functions.

Listing 16.2 The Full Text of Mammal2.cpp


 1: #include <iostream>
 2:
 3: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
 4:
 5: class Mammal
 6: {
 7: public:
 8:     // constructors
 9:     Mammal(): age(2), weight(5) {}
10:     ~Mammal(){}
11:
12:     // accessors
13:     int getAge() const { return age; }
14:     void setAge(int newAge) { age = newAge; }
15:     int getWeight() const { return weight; }
16:     void setWeight(int newWeight) { weight = newWeight; }
17:
18:     // other methods
19:     void speak() const { std::cout << "Mammal sound! "; }
20:     void sleep() const { std::cout << "Shhh. I'm sleeping. "; }
21:
22: protected:
23:     int age;
24:     int weight;
25: };
26:
27: class Dog : public Mammal
28: {
29: public:
30:     // constructors
31:     Dog(): breed(YORKIE) {}
32:     ~Dog() {}
33:
34:     // accessors
35:     BREED getBreed() const { return breed; }
36:     void setBreed(BREED newBreed) { breed = newBreed; }
37:
38:     // other methods
39:     void wagTail() { std::cout << "Tail wagging ... "; }
40:     void begForFood() { std::cout << "Begging for food ... "; }
41:
42: private:
43:     BREED breed;
44: };
45:
46: int main()
47: {
48:     Dog fido;
49:     fido.speak();
50:     fido.wagTail();
51:     std::cout << "Fido is " << fido.getAge() << " years old ";
52:     return 0;
53: }


When you run Mammal2, this output appears:

Mammal sound!
Tail wagging ...
Fido is 2 years old

On lines 5–25, the Mammal class is declared with several inline member functions. On lines 27–44, the Dog class is declared as a derived class of Mammal. These declarations give all Dog objects an age, weight, and breed.

On line 48, a Dog is declared called fido, which inherits all the attributes of a Mammal and the attributes of a Dog. Thus, fido knows how to wagTail(), speak(), and sleep().

Constructors and Destructors

An important aspect to understand about inheritance in C++ is that more than one constructor is called when an object of a derived class is created.

Dog objects are Mammal objects. When fido was created in the Mammal2 program, his base class constructor was called first, creating a Mammal, and then the Dog constructor was called, completing the construction of the Dog object. Because fido was created with no parameters, the default constructor was called in each case.

When the fido object is destroyed, the Dog destructor is called first, followed by the destructor for the Mammal part of Fido. Each destructor is given an opportunity to clean up after its own part of the object. Constructors are called in order of inheritance. Destructors are called in reverse order of inheritance.

The Mammal3 program in Listing 16.3 demonstrates how constructors and destructors are called for objects belonging to derived classes.

Listing 16.3 The Full Text of Mammal3.cpp


 1: #include <iostream>
 2:
 3: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
 4:
 5: class Mammal
 6: {
 7: public:
 8:     // constructors
 9:     Mammal();
10:     ~Mammal();
11:
12:     // accessors
13:     int getAge() const { return age; }
14:     void setAge(int newAge) { age = newAge; }
15:     int getWeight() const { return weight; }
16:     void setWeight(int newWeight) { weight = newWeight; }
17:
18:     // other methods
19:     void speak() const { std::cout << "Mammal sound! "; }
20:     void sleep() const { std::cout << "shhh. I'm sleeping. "; }
21:
22: protected:
23:     int age;
24:     int weight;
25: };
26:
27: class Dog : public Mammal
28: {
29: public:
30:     // constructors
31:     Dog();
32:     ~Dog();
33:
34:     // accessors
35:     BREED getBreed() const { return breed; }
36:     void setBreed(BREED newBreed) { breed = newBreed; }
37:
38:     // other methods
39:     void wagTail() { std::cout << "Tail wagging ... "; }
40:     void begForFood() { std::cout << "Begging for food ... "; }
41:
42: private:
43:     BREED breed;
44: };
45:
46: Mammal::Mammal():
47: age(1),
48: weight(5)
49: {
50:     std::cout << "Mammal constructor ... ";
51: }
52:
53: Mammal::~Mammal()
54: {
55:     std::cout << "Mammal destructor ... ";
56: }
57:
58: Dog::Dog():
59: breed(YORKIE)
60: {
61:     std::cout << "Dog constructor ... ";
62: }
63:
64: Dog::~Dog()
65: {
66:     std::cout << "Dog destructor ... ";
67: }
68:
69: int main()
70: {
71:     Dog fido; // create a dog
72:     fido.speak();
73:     fido.wagTail();
74:     std::cout << "Fido is " << fido.getAge() << " years old ";
75:     return 0;
76: }


Here’s the program’s output:

Mammal constructor ...
Dog constructor ...
Mammal sound!
Tail wagging ...
Fido is 1 years old
Dog destructor ...
Mammal destructor ...

The Mammal3 program displays text as constructors and destructors are called. When fido is created, Mammal’s constructor is called, and then Dog’s constructor. At that point the Dog fully exists, and its member functions can be called. When fido goes out of scope as the main() function ends on line 76, Dog’s destructor is called, followed by a call to Mammal’s destructor.

Passing Arguments to Base Constructors

It is possible that you’ll want to overload the constructor of Mammal to set a specific age and overload the Dog constructor to set a breed. How do you get the age and weight parameters passed up to the right constructor in Mammal? What if Dog needs to initialize weight but Mammal doesn’t?

Base class initialization can be performed during class initialization by writing the base class name followed by the parameters expected by the base class, as shown in the Mammal4 program (Listing 16.4).

Listing 16.4 The Full Text of Mammal4.cpp


  1: #include <iostream>
  2:
  3: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
  4:
  5: class Mammal
  6: {
  7: public:
  8:     // constructors
  9:     Mammal();
 10:     Mammal(int age);
 11:     ~Mammal();
 12:
 13:     // accessors
 14:     int getAge() const { return age; }
 15:     void setAge(int newAge) { age = newAge; }
 16:     int getWeight() const { return weight; }
 17:     void setWeight(int newWeight) { weight = newWeight; }
 18:
 19:     // other methods
 20:     void speak() const { std::cout << "Mammal sound! "; }
 21:     void sleep() const { std::cout << "Shhh. I'm sleeping. "; }
 22:
 23: protected:
 24:     int age;
 25:     int weight;
 26: };
 27:
 28: class Dog : public Mammal
 29: {
 30: public:
 31:     // constructors
 32:     Dog();
 33:     Dog(int age);
 34:     Dog(int age, int weight);
 35:     Dog(int age, BREED breed);
 36:     Dog(int age, int weight, BREED breed);
 37:     ~Dog();
 38:
 39:     // accessors
 40:     BREED getBreed() const { return breed; }
 41:     void setBreed(BREED newBreed) { breed = newBreed; }
 42:
 43:     // other methods
 44:     void wagTail() { std::cout << "Tail wagging ... "; }
 45:     void begForFood() { std::cout << "Begging for food ... "; }
 46:
 47: private:
 48:     BREED breed;
 49: };
 50:
 51: Mammal::Mammal():
 52: age(1),
 53: weight(5)
 54: {
 55:     std::cout << "Mammal constructor ... ";
 56: }
 57:
 58: Mammal::Mammal(int age):
 59: age(age),
 60: weight(5)
 61: {
 62:     std::cout << "Mammal(int) constructor ... ";
 63: }
 64:
 65: Mammal::~Mammal()
 66: {
 67:     std::cout << "Mammal destructor ... ";
 68: }
 69:
 70: Dog::Dog():
 71: Mammal(),
 72: breed(YORKIE)
 73: {
 74:     std::cout << "Dog constructor ... ";
 75: }
 76:
 77: Dog::Dog(int age):
 78: Mammal(age),
 79: breed(YORKIE)
 80: {
 81:     std::cout << "Dog(int) constructor ... ";
 82: }
 83:
 84: Dog::Dog(int age, int newWeight):
 85: Mammal(age),
 86: breed(YORKIE)
 87: {
 88:     weight = newWeight;
 89:     std::cout << "Dog(int, int) constructor ... ";
 90: }
 91:
 92: Dog::Dog(int age, int newWeight, BREED breed):
 93: Mammal(age),
 94: breed(breed)
 95: {
 96:     weight = newWeight;
 97:     std::cout << "Dog(int, int, BREED) constructor ... ";
 98: }
 99:
100: Dog::Dog(int age, BREED newBreed):
101: Mammal(age),
102: breed(newBreed)
103: {
104:     std::cout << "Dog(int, BREED) constructor ... ";
105: }
106:
107: Dog::~Dog()
108: {
109:     std::cout << "Dog destructor ... ";
110: }
111:
112: int main()
113: {
114:     Dog fido;
115:     Dog rover(5);
116:     Dog buster(6, 8);
117:     Dog yorkie (3, YORKIE);
118:     Dog dobbie (4, 20, DOBERMAN);
119:     fido.speak();
120:     rover.wagTail();
121:     std::cout << "Yorkie is "
122:         << yorkie.getAge() << " years old ";
123:     std::cout << "Dobbie weighs "
124:         << dobbie.getWeight() << " pounds ";
125:     return 0;
126: }


The Mammal4 program displays the following output, which has line numbers added so that they can be referenced in this section (but that don’t appear in the actual output):

 1: Mammal constructor ...
 2: Dog constructor ...
 3: Mammal(int) constructor ...
 4: Dog(int) constructor ...
 5: Mammal(int) constructor ...
 6: Dog(int, int) constructor ...
 7: Mammal(int) constructor ...
 8: Dog(int, BREED) constructor ...
 9: Mammal(int) constructor ...
10: Dog(int, int, BREED) constructor ...
11: Mammal sound!
12: Tail wagging ...
13: Yorkie is 3 years old
14: Dobbie weighs 20 pounds
15: Dog destructor ...
16: Mammal destructor ...
17: Dog destructor ...
18: Mammal destructor ...
19: Dog destructor ...
20: Mammal destructor ...
21: Dog destructor ...
22: Mammal destructor ...
23: Dog destructor ...
24: Mammal destructor ...

In Listing 16.4, Mammal’s constructor has been overloaded on line 10 to take an integer, the Mammal object’s age. The implementation on lines 58–63 initializes age with the value passed into the constructor, and weight with the value 5.

Dog has overloaded five constructors on lines 32–36. The first is the default constructor. The second takes the age, which is the same parameter that the Mammal constructor takes. The third constructor takes both the age and the weight; the fourth takes the age and breed; and the fifth takes the age, weight, and breed.


By the Way

Note that on line 71, Dog’s default constructor calls Mammal’s default constructor. Although it is not strictly necessary to do this, it serves as documentation that you intended to call the base constructor, which takes no parameters. The base constructor would be called in any case, but actually doing so makes your intentions explicit.


The implementation for the Dog constructor, which takes an integer, is on lines 77–82. In its initialization phase (lines 78–79), Dog initializes its base class, passing in the parameter; and then it initializes its breed.

Another Dog constructor is on lines 84–90. This one takes two parameters. It initializes its base class by calling the appropriate constructor, but this time also assigns weight to its base class’s variable weight. Note that you cannot assign to the base class variable in the initialization phase. So, you cannot write this code:

Dog::Dog(int age, int newWeight):
Mammal(age),
breed(YORKIE),
weight(newWeight) // error!
{
    std::cout << "Dog(int, int) constructor ... ";
}

Why? You are not allowed to initialize a value in the base class. Similarly, you may not write the following:

Dog::Dog(int newAge, int newWeight):
Mammal(newAge, newWeight), // error!
breed(YORKIE)
{
    std::cout << "Dog(int, int) constructor ... ";
}

Mammal does not have a constructor that takes the weight parameter. You must do this assignment within the body of the Dog constructor.

Dog::Dog(int newAge, int weight):
Mammal(newAge), // base constructor
breed(YORKIE) // initialization
{
    weight = weight; // assignment
}

Walk through the remaining constructors to make sure you are comfortable with how they work. Take note of what is initialized and what must wait for the body of the constructor.

The output has been numbered so that each line can be referred to in this analysis. The first two lines of output represent the instantiation of fido, using the default constructor.

In the output, lines 3 and 4 represent the creation of rover. Lines 5 and 6 represent buster. Note that the Mammal constructor that was called is the constructor that takes one integer, but the Dog constructor is the constructor that takes two integers.

After all the objects are created, they are used and then go out of scope. As each object is destroyed, first the Dog destructor and then the Mammal destructor is called; there are five of each in total.

This is an example of overloading base class member functions within a derived class.

Overriding Functions

A Dog object has access to all the member functions in class Mammal, as well as to any member functions, such as wagTail(), that the declaration of the Dog class might add. It also can override a base class function. Overriding a function means changing the implementation of a base class function in a derived class. When you make an object of the derived class, the correct function is called.

When a derived class creates a member function with the same return type and signature as a member function in the base class, but with a new implementation, it is said to be overriding that function.

When you override a function, it must agree in return type and in signature with the function in the base class. The signature is the function prototype other than the return type: that is, the name, the parameter list, and the keyword const, if used.

The signature of a function is its name, and the number and type of its parameters. The signature does not include the return type.

The Mammal5 program (Listing 16.5) illustrates what happens if the Dog class overrides the speak() method in Mammal. To save room, the accessor functions have been left out of these classes.

Listing 16.5 The Full Text of Mammal5.cpp


 1: #include <iostream>
 2:
 3: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
 4:
 5: class Mammal
 6: {
 7: public:
 8:     // constructors
 9:     Mammal() { std::cout << "Mammal constructor ... "; }
10:     ~Mammal() { std::cout << "Mammal destructor ... "; }
11:
12:     // other methods
13:     void speak() const { std::cout << "Mammal sound! "; }
14:     void sleep() const { std::cout << "Shhh. I'm sleeping. "; }
15:
16: protected:
17:     int age;
18:     int weight;
19: };
20:
21: class Dog : public Mammal
22: {
23: public:
24:     // constructors
25:     Dog() { std::cout << "Dog constructor ... "; }
26:     ~Dog() { std::cout << "Dog destructor ... "; }
27:
28:     // other methods
29:     void wagTail() { std::cout << "Tail wagging ... "; }
30:     void begForFood() { std::cout << "Begging for food ... "; }
31:     void speak() const { std::cout << "Woof! "; }
32:
33: private:
34:     BREED breed;
35: };
36:
37: int main()
38: {
39:     Mammal bigAnimal;
40:     Dog fido;
41:     bigAnimal.speak();
42:     fido.speak();
43:     return 0;
44: }


Here’s the output of Mammal5:

Mammal constructor ...
Mammal constructor ...
Dog constructor ...
Mammal sound!
Woof!
Dog destructor ...
Mammal destructor ...
Mammal destructor ...

On line 31, the Dog class overrides the speak() method, causing Dog objects to say “Woof!” when the speak() method is called. On line 39, a Mammal object, bigAnimal, is created, causing the first line of output to be displayed when the Mammal constructor is called. On line 40, a Dog object, fido, is created, causing the next two lines of output, where the Mammal constructor and then the Dog constructor are called.

On line 41, the Mammal object calls its speak() method; then on line 42 the Dog object calls its speak() method. The output reflects that the correct methods were called. Finally, the two objects go out of scope and the destructors are called.

Overloading Versus Overriding

The terms overloading and overriding are similar and do similar things in C++. When you overload a member function, you create more than one function with the same name but with different signatures. When you override a member function, you create a function in a derived class with the same name as a function in the base class and with the same signature.

Hiding the Base Class Method

In the Mammal5 program, the Dog class’s member function speak() hides the base class’s function. This is just what is wanted, but it can have unexpected results. If Mammal has a move() method that is overloaded, and Dog overrides that function, the Dog method hides all the Mammal functions with that name.

If Mammal overloads move() as three functions—one that takes no parameters, one that takes an integer, and one that takes an integer and a direction—and Dog overrides just the move() method, which takes no parameters, it will not be easy to access the other two methods using a Dog object. The Mammal6 program in Listing 16.6 illustrates this problem.

Listing 16.6 The Full Text of Mammal6.cpp


 1: #include <iostream>
 2:
 3: class Mammal
 4: {
 5: public:
 6:     void move() const { std::cout << "Mammal moves one step "; }
 7:     void move(int distance) const
 8:         { std::cout << "Mammal moves " << distance <<" steps "; }
 9: protected:
10:     int age;
11:     int weight;
12: };
13:
14: class Dog : public Mammal
15: {
16: public:
17:     void move() const { std::cout << "Dog moves 5 steps "; }
18: }; // you may receive a warning that you are hiding a function!
19:
20: int main()
21: {
22:     Mammal bigAnimal;
23:     Dog fido;
24:     bigAnimal.move();
25:     bigAnimal.move(2);
26:     fido.move();
27:     // fido.move(10);
28:     return 0;
29: }


The Mammal6 program produces this output when run:

Mammal moves one step
Mammal moves 2 steps
Dog moves 5 steps

All the extra methods and data have been removed from these classes. On lines 6–8, the Mammal class declares the overloaded move() methods. On line 17, Dog overrides the version of move() with no parameters. These are invoked on lines 24–26, and the output reflects this as executed.

Line 27, however, is commented out, as it causes a compile-time error. Although the Dog class could have called the move(int) method if it had not overridden the version of move() without parameters, now that it has done so it must override both to use both. This is reminiscent of the rule that states if you supply any constructor the compiler will no longer supply a default constructor.

It is a common mistake to hide a base class method, when you intend to override it, by forgetting to include the keyword const. That keyword is part of the signature, and leaving it off changes the signature and thus hides the member function instead of overriding it.

Some compilers will give you a warning somewhere around lines 15–18. Although you are allowed to hide base class member functions from derived classes, it often is done by mistake, which is why some compilers issue a warning.

Calling the Base Method

If you have overridden the base method, it is still possible to call it by fully qualifying the name of the method. You do this by writing the base name, followed by two colons and then the method name. For example:

Mammal::move()

It would have been possible to rewrite line 27 in Listing 16.6 so that it would compile:

28:     fido.Mammal::move(10);

This calls the Mammal method explicitly. This hour’s last project, the Mammal7 program in Listing 16.7, fully illustrates this idea.

Listing 16.7 The Full Text of Mammal7.cpp


 1: #include <iostream>
 2:
 3: class Mammal
 4: {
 5: public:
 6:     void move() const { std::cout << "Mammal moves one step "; }
 7:     void move(int distance) const
 8:         { std::cout << "Mammal moves " << distance << " steps "; }
 9: protected:
10:     int age;
11:     int weight;
12: };
13:
14: class Dog : public Mammal
15: {
16: public:
17:     void move() const;
18: };
19:
20: void Dog::move() const
21: {
22:     std::cout << "Dog moves ... ";
23:     Mammal::move(3);
24: }
25:
26: int main()
27: {
28:     Mammal bigAnimal;
29:     Dog fido;
30:     bigAnimal.move(2);
31:     fido.Mammal::move(6);
32:     return 0;
33: }


Mammal6 produces this output when run:

Mammal moves 2 steps
Mammal moves 6 steps

On line 28, a Mammal, bigAnimal, is created; and on line 29, a Dog called fido is created. The method call on line 30 invokes the move() method of Mammal, which takes an integer.

If you wanted to invoke move(int) on the Dog object, there’s a problem. Dog overrides the move() method but doesn’t overload it and does not provide a version that takes an int. This is solved by the explicit call to the base class move(int) method on line 31.

Summary

If this is your first introduction to class inheritance, you might find yourself wondering if the work of creating base classes and derived classes is worth the effort. Deciding where to put member variables and functions among a set of related classes can take some time and planning.

The reason to do the work is that it makes your classes more powerful and reusable.

When you’ve designed a set of related classes well, you can extend a base class and design a new derived class much more easily. You only have to focus on the things that make the new class different from its parent.

Q&A

Q. Are inherited members and functions passed along to subsequent generations? If Dog derives from Mammal and Mammal derives from Animal, does Dog inherit Animal's functions and data?

A. Yes. As derivation continues, derived classes inherit the sum of all the functions and data in all their base classes.

Q. Can a derived class make a public base function private?

A. Yes, and it will then remain private for all subsequent derivations.

Q. When did Constantinople become Istanbul?

A. The city of Istanbul, Turkey, became known as Constantinople during the reign of the East Roman emperor Theodosius II from 408 to 450. The name honored the emperor Constantine the Great, who had made the city the eastern capital of the Roman empire.

Some centuries later, the name Istanbul began to be used as well, which roughly translates to mean “The City.”

After the creation of the Republic of Turkey, a law was passed in 1930 that asked the rest of the world to start calling the city Istanbul. To put some teeth in the request, the Turkish postal service began rejecting packages addressed to Constantinople.

The name change inspired the 1953 song “Istanbul (Not Constantinople),” performed by the Four Lads, and a 1990 remake by They Might Be Giants.

“Why did Constantinople get the works?” the song asks. “That’s nobody’s business but the Turks.”

Workshop

Now that you’ve had the chance to work with inheritance, you can answer a few questions and do a couple of exercises to firm up your knowledge.

Quiz

1. What access keyword limits a member variable to an object and its derived classes?

A. public

B. private

C. protected

2. In what order are constructors called in a derived class?

A. In order of inheritance downward

B. In order of inheritance upward

C. In order the classes are defined in the source code

3. Why would you want to hide a base class member function in a derived class?

A. To prevent it from being used

B. To call that function

C. There’s no reason to do this

Answers

1. C. The protected keyword limits access to member variables and member functions so that derived classes can use them but no other classes can.

2. A. The base class constructor is called first, followed by the derived class constructor.

3. A. Sometimes the behavior of the derived class is different enough from the base that some of the base member functions are inappropriate. Because it is not always possible to modify the base class (if you do not have the source for it, for example), this is the mechanism to use.

Activities

1. In the Mammal6 program (Listing 16.6), uncomment line 28. What happens? What do you have to do to make it work?

2. Modify the Mammal2 program (Listing 16.2) to use a text string rather than an enumerated data type as the breed.

To see solutions to these activities, visit this book’s website at http://cplusplus.cadenhead.org.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.135.81