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.
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.
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.
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.
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()
.
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.
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.
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.
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.
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.
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.
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. 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.”
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.
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
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.
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.
18.119.135.81