Object-oriented programming is based on four important aspects: encapsulation, abstraction, inheritance, and polymorphism. Inheritance is a powerful way to reuse attributes and is a stepping stone towards polymorphism.
In this lesson, you find out about
Inheritance in the context of programming
The C++ syntax of inheritance
public
, private
, and protected
inheritance
Multiple inheritance
Problems caused by hiding base class methods and slicing
What Tom Smith inherits from his forefathers is first and foremost his family name that makes him a Smith. In addition, he inherits certain values that his parents have taught him and a skill at sculpting wood that has been the Smith family occupation for many generations. These attributes collectively identify Tom as an offspring of the Smith family tree.
In programming parlance, you are often faced with situations where components being managed have similar attributes, differing minutely in details or in behavior. One way to solve this problem is to make each component a class where each class implements all attributes and re-implements the common ones. Another solution is using inheritance to allow classes that are similar to derive from a base class that contains common attributes and implements common functionality, overriding this base functionality to implement behavior that makes each class unique. The latter is often the preferred way. Welcome to inheritance in our world of object-oriented programming, as illustrated by Figure 10.1.
Figure 10.1 shows a diagrammatic relationship between a base class and its derived classes. It might not be easy right now to visualize what a base class or a derived class could be. Try to understand that a derived class inherits from the base class and in that sense is a base class (just like Tom is a Smith).
Note
The is-a relationship between a derived class and its base is applicable only to public
inheritance. This lesson starts with public inheritance to understand the concept of inheritance and the most frequent form of inheritance before moving on to private
or protected
inheritance.
To make understanding this concept easy, think of a base class Bird
. Classes are derived from Bird
are class Crow
, class Parrot
, or class Kiwi
. A class Bird
would define the most basic attributes of a bird, such as “is feathered,” “has wings,” “lays eggs,” “can fly,” and so on. Derived classes such as Crow
, Parrot
, or Kiwi
inherit these attributes and customize them (for example, a class Kiwi
that represents a flightless-bird would contain no implementation of Fly()
). Table 10.1 demonstrates a few more examples of inheritance.
What these examples show is that when you put on your object-oriented programming glasses, you see examples of inheritance in many objects around yourself. Fish
is a base class for a Tuna
because a Tuna
, like a Carp
, is a Fish
and presents all fish-like characteristics such as being cold-blooded. However, Tuna
differs from a Carp
in the way it looks, swims, and in the fact that it is a saltwater fish. Thus, Tuna
and Carp
inherit common characteristics from a common base class Fish
, yet specialize the base class attributes to distinguish themselves from each other. This is illustrated in Figure 10.2.
A platypus can swim, yet is a special animal with mammalian characteristics such as feeding its young with milk, avian (bird-like) characteristics as it lays eggs, and reptilian characteristics as it is venomous. Thus, one can imagine a class Platypus
inheriting from two base classes, class Mammal
and class Bird
, to inherit mammalian and avian features. This form of inheritance is called multiple inheritance, which is discussed later in this lesson.
How would you inherit class Carp
from class Fish,
or in general a class Derived
from class Base
? C++ syntax for doing this would be the following:
class Base
{
// ... base class members
};
class Derived: access-specifier Base
{
// ... derived class members
};
The access-specifier
can be one of public
(most frequently used) where a “derived class is a base class” relationship; private
or protected
for a “derived class has a base class” relationship.
An inheritance hierarchical representation for a class Carp
that derives from class Fish
would be
class Fish // base class
{
// ... Fish's members
};
class Carp:public Fish // derived class
{
// ... Carp's members
};
A compile-worthy declaration of a class Carp
and class Tuna
that derive from class Fish
is demonstrated by Listing 10.1.
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: public:
6: bool isFreshWaterFish;
7:
8: void Swim()
9: {
10: if (isFreshWaterFish)
11: cout << "Swims in lake" << endl;
12: else
13: cout << "Swims in sea" << endl;
14: }
15: };
16:
17: class Tuna: public Fish
18: {
19: public:
20: Tuna()
21: {
22: isFreshWaterFish = false;
23: }
24: };
25:
26: class Carp: public Fish
27: {
28: public:
29: Carp()
30: {
31: isFreshWaterFish = true;
32: }
33: };
34:
35: int main()
36: {
37: Carp myLunch;
38: Tuna myDinner;
39:
40: cout << "About my food:" << endl;
41:
42: cout << "Lunch: ";
43: myLunch.Swim();
44:
45: cout << "Dinner: ";
46: myDinner.Swim();
47:
48: return 0;
49: }
Output
About my food:
Lunch: Swims in lake
Dinner: Swims in sea
Analysis
Note Lines 37 and 38 in main()
that create an instance of classes Carp
and Tuna
, respectively, called myLunch
and myDinner
. Lines 43 and 46 are where I ask my lunch and dinner to swim by invoking method Swim()
. Now, look at the class definitions of Tuna
in Lines 17–24 and Carp
in Lines 26–33. As you can see, these classes are compact with their constructors setting the Boolean flag Fish::isFreshWaterFish
to the appropriate values. This flag is later used in function Fish::Swim()
. Neither of the two derived classes seems to define a method Swim()
that you have managed to successfully invoke in main()
. This is because Swim()
is a public member of base class Fish
that they inherit from, defined in Lines 3–15. This public inheritance in Lines 17 and 26 automatically exposes the base class’s public members, including method Swim()
, through instances of the derived classes Carp
and Tuna
, which we invoke in main()
.
protected
Listing 10.1 is one where class Fish
has a public attribute isFreshWaterFish
that is set by the derived classes Tuna
and Carp
so as to customize (also called specialize) the behavior of Fish
and adapt it to saltwater and freshwater, respectively. However, Listing 10.1 exhibits a serious flaw: If you want, even main()
could tamper with isFreshWaterFish, which is public
and hence open for manipulation from outside class Fish
:
myDinner.isFreshWaterFish = true; // but Tuna isn't a fresh water fish!
Apparently, you need a mechanism that allows derived class members to modify chosen attributes of the base class, while denying access to the same from everyone else. This means that you want Boolean flag isFreshWaterFish
in class Fish
to be accessible to class Tuna
and class Carp
, but not accessible to main()
that instantiates classes Tuna
or Carp
. This is where keyword protected
helps you.
Note
protected,
like public
and private
, is also an access specifier. When you declare a class attribute or function as protected
, you are effectively making it accessible to classes that derive (and friends), yet simultaneously making it inaccessible to everyone else outside the class, including main()
.
protected
is the access specifier you should use if you want a certain attribute in a base class to be accessible to classes that derive from this base, as demonstrated in Listing 10.2.
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: protected:
6: bool isFreshWaterFish; // accessible only to derived classes
7:
8: public:
9: void Swim()
10: {
11: if (isFreshWaterFish)
12: cout << "Swims in lake" << endl;
13: else
14: cout << "Swims in sea" << endl;
15: }
16: };
17:
18: class Tuna: public Fish
19: {
20: public:
21: Tuna()
22: {
23: isFreshWaterFish = false; // set protected member in base
24: }
25: };
26:
27: class Carp: public Fish
28: {
29: public:
30: Carp()
31: {
32: isFreshWaterFish = false;
33: }
34: };
35:
36: int main()
37: {
38: Carp myLunch;
39: Tuna myDinner;
40:
41: cout << "About my food" << endl;
42:
43: cout << "Lunch: ";
44: myLunch.Swim();
45:
46: cout << "Dinner: ";
47: myDinner.Swim();
48:
49: // uncomment line below to see that protected members
50: // are not accessible from outside the class hierarchy
51: // myLunch.isFreshWaterFish = false;
52:
53: return 0;
54: }
Output
About my food
Lunch: Swims in lake
Dinner: Swims in sea
Analysis
In spite of the fact that the output of Listing 10.2 is the same as Listing 10.1, there are a good number of fundamental changes to class Fish
as defined in Lines 3–16. The first and most evident change is that the Boolean member Fish::isFreshWaterFish
is now a protected
attribute, and hence, not accessible via main()
as shown in Line 51 (uncomment it to see a compiler error). All the same, this member of Fish
with access specifier protected
is accessible from the derived classes Tuna
and Carp
as shown in Lines 23 and 32, respectively. What this little program effectively demonstrates is the use of keyword protected
in ensuring that base class attributes that need to be inherited are protected from being accessed outside the class hierarchy.
This is an important aspect of object-oriented programming, combining data abstraction and inheritance, in ensuring that derived classes can safely inherit base class attributes that cannot be tampered with by anyone outside this hierarchical system.
What if a base class were to contain an overloaded constructor that requires arguments at the time of instantiation? How would such a base class be instantiated when the derived class is being constructed? The clue lies in using initialization lists and in invoking the appropriate base class constructor via the constructor of the derived class as shown in the following code:
class Base
{
public:
Base(int someNumber) // overloaded constructor
{
// Use someNumber
}
};
Class Derived: public Base
{
public:
Derived(): Base(25) // instantiate Base with argument 25
{
// derived class constructor code
}
};
This mechanism can be quite useful in class Fish
wherein, by supplying a Boolean input parameter to the constructor of Fish
that initializes Fish::isFreshWaterFish
, this base class Fish
can ensure that every derived class is forced to mention whether the Fish
is a freshwater one or a saltwater one as shown in Listing 10.3.
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: protected:
6: bool isFreshWaterFish; // accessible only to derived classes
7:
8: public:
9: // Fish constructor
10: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){}
11:
12: void Swim()
13: {
14: if (isFreshWaterFish)
15: cout << "Swims in lake" << endl;
16: else
17: cout << "Swims in sea" << endl;
18: }
19: };
20:
21: class Tuna: public Fish
22: {
23: public:
24: Tuna(): Fish(false) {} // constructor initializes base
25: };
26:
27: class Carp: public Fish
28: {
29: public:
30: Carp(): Fish(true) {}
31: };
32:
33: int main()
34: {
35: Carp myLunch;
36: Tuna myDinner;
37:
38: cout << "About my food" << endl;
39:
40: cout << "Lunch: ";
41: myLunch.Swim();
42:
43: cout << "Dinner: ";
44: myDinner.Swim();
45:
46: return 0;
47: }
Output
About my food
Lunch: Swims in lake
Dinner: Swims in sea
Fish
now has a constructor that takes a default parameter initializing Fish::is FreshWaterFish
. Thus, the only possibility to create an object of Fish
is via providing it a parameter that initialized the protected member. This way class Fish
ensures that the protected member doesn’t contain a random value, especially if a derived class forgets to set it. Derived classes Tuna
and Carp
are now forced to define a constructor that instantiates the base class instance of Fish
with the right parameter (true or false, indicating freshwater or otherwise), as shown in Lines 24 and 30, respectively.
Note
In Listing 10.3 you see that boolean member variable Fish::isFreshWaterFish
was never accessed directly by a derived class in spite of it being a protected
member, as this variable was set via the constructor of Fish.
To ensure maximum security, if the derived classes don’t need to access a base class attribute, remember to mark the attribute private
. Therefore, a superior version of Listing 10.3 would feature Fish::isFreshWaterFish
as private
, for it is consumed only by base class Fish
. See Listing 10.4.
If a class Derived
implements the same functions with the same return values and signatures as in a class Base
it inherits from, it effectively overrides that method in class Base
as shown in the following code:
class Base
{
public:
void DoSomething()
{
// implementation code... Does something
}
};
class Derived:public Base
{
public:
void DoSomething()
{
// implementation code... Does something else
}
};
Thus, if method DoSomething()
were to be invoked using an instance of Derived
, then it would not invoke the functionality in class Base
.
If classes Tuna
and Carp
were to implement their own Swim()
method that also exists in the base class as Fish::Swim()
, then a call to Swim
as shown in main()
from the following excerpt of Listing 10.3
36: Tuna myDinner;
// ...other lines
44: myDinner.Swim();
would result in the local implementation of Tuna::Swim()
being invoked, which essentially overrides the base class’s Fish::Swim()
method. This is demonstrated by Listing 10.4.
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: private:
6: bool isFreshWaterFish;
7:
8: public:
9: // Fish constructor
10: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){}
11:
12: void Swim()
13: {
14: if (isFreshWaterFish)
15: cout << "Swims in lake" << endl;
16: else
17: cout << "Swims in sea" << endl;
18: }
19: };
20:
21: class Tuna: public Fish
22: {
23: public:
24: Tuna(): Fish(false) {}
25:
26: void Swim()
27: {
28: cout << "Tuna swims real fast" << endl;
29: }
30: };
31:
32: class Carp: public Fish
33: {
34: public:
35: Carp(): Fish(true) {}
36:
37: void Swim()
38: {
39: cout << "Carp swims real slow" << endl;
40: }
41: };
42:
43: int main()
44: {
45: Carp myLunch;
46: Tuna myDinner;
47:
48: cout << "About my food" << endl;
49:
50: cout << "Lunch: ";
51: myLunch.Swim();
52:
53: cout << "Dinner: ";
54: myDinner.Swim();
55:
56: return 0;
57: }
Output
About my food
Lunch: Carp swims real slow
Dinner: Tuna swims real fast
Analysis
The output demonstrates that myLunch.Swim()
in Line 51 invokes Carp::Swim()
defined in Lines 37–40. Similarly, myDinner.Swim()
from Line 54 invokes Tuna::Swim()
defined in Lines 26–29. In other words, the implementation of Fish::Swim()
in the base class Fish
, as shown in Lines 12–18, is overridden by the identical function Swim()
defined by the classes Tuna
and Carp
that derive from Fish
. The only way to invoke Fish::Swim()
is by having main()
use the scope resolution operator (::
) in explicitly invoking Fish::Swim()
, as shown later in this lesson.
In Listing 10.4, you saw an example of derived class Tuna
overriding the Swim()
function in Fish
by implementing its version of the same. Essentially:
Tuna myDinner;
myDinner.Swim(); // will invoke Tuna::Swim()
If you want to be invoke Fish::Swim()
in Listing 10.4 via main()
, you need to use the scope resolution operator (::) in the following syntax:
myDinner.Fish::Swim(); // invokes Fish::Swim() using instance of Tuna
Listing 10.5 that follows shortly demonstrates invoking a base class member using an instance of the derived class.
Typically, Fish::Swim()
would contain a generic implementation of swimming applicable to all fishes, tunas, and carps included. If your specialized implementations in Tuna:Swim()
and Carp::Swim()
need to reuse the base class’s generic implementation of Fish::Swim()
, you use the scope resolution operator (::
) as shown in the following code:
class Carp: public Fish
{
public:
Carp(): Fish(true) {}
void Swim()
{
cout << "Carp swims real slow" << endl;
Fish::Swim(); // invoke base class function using operator::
}
};
This is demonstrated in Listing 10.5.
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: private:
6: bool isFreshWaterFish;
7:
8: public:
9: // Fish constructor
10: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){}
11:
12: void Swim()
13: {
14: if (isFreshWaterFish)
15: cout << "Swims in lake" << endl;
16: else
17: cout << "Swims in sea" << endl;
18: }
19: };
20:
21: class Tuna: public Fish
22: {
23: public:
24: Tuna(): Fish(false) {}
25:
26: void Swim()
27: {
28: cout << "Tuna swims real fast" << endl;
29: }
30: };
31:
32: class Carp: public Fish
33: {
34: public:
35: Carp(): Fish(true) {}
36:
37: void Swim()
38: {
39: cout << "Carp swims real slow" << endl;
40: Fish::Swim();
41: }
42: };
43:
44: int main()
45: {
46: Carp myLunch;
47: Tuna myDinner;
48:
49: cout << "About my food" << endl;
50:
51: cout << "Lunch: ";
52: myLunch.Swim();
53:
54: cout << "Dinner: ";
55: myDinner.Fish::Swim();
56:
57: return 0;
58: }
Output
About my food
Lunch: Carp swims real slow
Swims in lake
Dinner: Swims in sea
Carp::Swim()
in Lines 37–41 demonstrates calling the base class function Fish::Swim()
using the scope resolution operator (::
). Line 55, on the other hand, shows how you would use the scope resolution operator (::
) to invoke base class method Fish::Swim()
from main()
given an instance of derived class Tuna
.
Overriding can take an extreme form where Tuna::Swim()
can potentially hide all overloaded versions of Fish::Swim()
available, even causing compilation failure when the overloaded ones are used (hence, called hidden), as demonstrated by Listing 10.6.
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: public:
6: void Swim()
7: {
8: cout << "Fish swims... !" << endl;
9: }
10:
11: void Swim(bool isFreshWaterFish) // overloaded version
12: {
13: if (isFreshWaterFish)
14: cout << "Swims in lake" << endl;
15: else
16: cout << "Swims in sea" << endl;
17: }
18: };
19:
20: class Tuna: public Fish
21: {
22: public:
23: void Swim()
24: {
25: cout << "Tuna swims real fast" << endl;
26: }
27: };
28:
29: int main()
30: {
31: Tuna myDinner;
32:
33: cout << "About my food" << endl;
34:
35: // myDinner.Swim(false);//failure: Tuna::Swim() hides Fish::Swim(bool)
36: myDinner.Swim();
37:
38: return 0;
39: }
Output
About my food
Tuna swims real fast
Analysis
This version of class Fish
is a bit different from those that you have seen so far. Apart from being a minimalized version to explain the problem at hand, this version of Fish
contains two overloaded methods for Swim()
, one that takes no parameters, as shown in Lines 6–9, and another that takes a bool
parameter, as shown in Lines 11–17. As Tuna
inherits public
from Fish
as shown in Line 20, one would not be wrong to expect that both versions of method Fish::Swim()
would be available via an instance of class Tuna
. The fact is, however, that Tuna
implementing its own Tuna::Swim()
, as shown in Lines 23–26, results in the hiding of Fish::Swim(bool)
from the compiler. If you uncomment Line 35, you see a compilation failure.
So, if you want to invoke the Fish::Swim(bool)
function via an instance of Tuna
, you have the following solutions:
Solution 1: Use the scope resolution operator in main()
:
myDinner.Fish::Swim();
Solution 2: Use keyword using
in class Tuna
to unhide Swim()
in class Fish
:
class Tuna: public Fish
{
public:
using Fish::Swim; // unhide all Swim() methods in class Fish
void Swim()
{
cout << "Tuna swims real fast" << endl;
}
};
Solution 3: Override all overloaded variants of Swim()
in class Tuna (invoke methods of Fish::Swim(...)
via Tuna::Fish(...)
if you want):
class Tuna: public Fish
{
public:
void Swim(bool isFreshWaterFish)
{
Fish::Swim(isFreshWaterFish);
}
void Swim()
{
cout << "Tuna swims real fast" << endl;
}
};
So, when you create an object of class Tuna
that derives from class Fish
, was the constructor of Tuna
invoked before or after the constructor of class Fish
? Additionally, within the instantiation of objects in the class hierarchy, what respective order do member attributes such as Fish::isFreshWaterFish
have? Thankfully, the instantiation sequence is standardized. Base class objects are instantiated before the derived class. So, the Fish
part of Tuna
is constructed first, so that member attributes—especially the protected and public ones contained in class Fish
—are ready for consumption when class Tuna
is instantiated. Within the instantiation of class Fish
and class Tuna
, the member attributes (such as Fish::isFreshWaterFish
) are instantiated before the constructor Fish::Fish()
is invoked, ensuring that member attributes are ready before the constructor works with them. The same applies to Tuna::Tuna()
.
When an instance of Tuna
goes out of scope, the sequence of destruction is the opposite to that of construction. Listing 10.7 is a simple example that demonstrates the sequence of construction and destruction.
0: #include <iostream>
1: using namespace std;
2:
3: class FishDummyMember
4: {
5: public:
6: FishDummyMember()
7: {
8: cout << "FishDummyMember constructor" << endl;
9: }
10:
11: ~FishDummyMember()
12: {
13: cout << "FishDummyMember destructor" << endl;
14: }
15: };
16:
17: class Fish
18: {
19: protected:
20: FishDummyMember dummy;
21:
22: public:
23: // Fish constructor
24: Fish()
25: {
26: cout << "Fish constructor” << endl;
27: }
28:
29: ~Fish()
30: {
31: cout << "Fish destructor" << endl;
32: }
33: };
34:
35: class TunaDummyMember
36: {
37: public:
38: TunaDummyMember()
39: {
40: cout << "TunaDummyMember constructor" << endl;
41: }
42:
43: ~TunaDummyMember()
44: {
45: cout << "TunaDummyMember destructor" << endl;
46: }
47: };
48:
49:
50: class Tuna: public Fish
51: {
52: private:
53: TunaDummyMember dummy;
54:
55: public:
56: Tuna()
57: {
58: cout << "Tuna constructor" << endl;
59: }
60: ~Tuna()
61: {
62: cout << "Tuna destructor" << endl;
63: }
64:
65: };
66:
67: int main()
68: {
69: Tuna myDinner;
70: }
Output
FishDummyMember constructor
Fish constructor
TunaDummyMember constructor
Tuna constructor
Tuna destructor
TunaDummyMember destructor
Fish destructor
FishDummyMember destructor
Analysis
main()
as shown in Lines 67–70 is pretty short for the volume of output it generates. Instantiation of a Tuna
is enough to generate these lines of output because of the cout
statements that you have inserted into the constructors and destructors of all objects involved. For the sake of understanding how member variables are instantiated and destroyed, you defined two dummy classes, FishDummyMember
, and TunaDummyMember
with cout
in their constructors and destructors. class Fish
and class Tuna
contain a member of each of these dummy classes as shown in Lines 20 and 53. The output indicates that when an object of class Tuna
is instantiated, instantiation actually starts at the top of the hierarchy. So, the base class Fish
part of class Tuna
is instantiated first, and in doing so, the members of the Fish
—that is, Fish::dummy
—are instantiated first. This is then followed by the constructor of the Fish
, which is rightfully executed after the member attributes such as dummy
have been constructed. After the base class has been constructed, the instantiation of Tuna
continues first with instantiation of member Tuna::dummy
, finally followed by the execution of the constructor code in Tuna::Tuna()
. The output demonstrates that the sequence of destruction is exactly the opposite.
Private inheritance differs from public inheritance (which is what you have seen up to now) in that the keyword private
is used in the line where the derived class declares its inheritance from a base class:
class Base
{
// ... base class members and methods
};
class Derived: private Base // private inheritance
{
// ... derived class members and methods
};
Private inheritance of the base class means that all public members and attributes of the base class are private (that is, inaccessible) to anyone with an instance of the derived class. In other words, even public members and methods of class Base
can only be consumed by class Derived
, but not by anyone else in possession of an instance of Derived
.
This is in sharp contrast to the examples with Tuna
and base Fish
that you have been following since Listing 10.1. main()
in Listing 10.1 could invoke function Fish::Swim()
on an instance of Tuna
because Fish::Swim()
is a public method and because class Tuna
derives from class Fish
using public
inheritance.
Thus, for the world outside the inheritance hierarchy, private
inheritance essentially does not imply an "is-a" relationship (imagine a tuna that can’t swim!). As private inheritance allows base class attributes and methods to be consumed only by the subclass that derives from it, this relationship is also called a “has-a” relationship. There are a few examples of private inheritance in some things you see around you in daily life (see Table 10.2).
Let’s visualize private inheritance in a car’s relationship to its motor. See Listing 10.8.
0: #include <iostream>
1: using namespace std;
2:
3: class Motor
4: {
5: public:
6: void SwitchIgnition()
7: {
8: cout << "Ignition ON" << endl;
9: }
10: void PumpFuel()
11: {
12: cout << "Fuel in cylinders" << endl;
13: }
14: void FireCylinders()
15: {
16: cout << "Vroooom" << endl;
17: }
18: };
19:
20: class Car:private Motor // private inheritance
21: {
22: public:
23: void Move()
24: {
25: SwitchIgnition();
26: PumpFuel();
27: FireCylinders();
28: }
29: };
30:
31: int main()
32: {
33: Car myDreamCar;
34: myDreamCar.Move();
35:
36: return 0;
37: }
Output
Ignition ON
Fuel in cylinders
Vroooom
class Motor
defined in Lines 3–18 is simple with three public
member functions that switch ignition, pump fuel, and fire the cylinders. class Car
as Line 20 demonstrates inherits from Motor
, using keyword private
. Thus, public function Car::Move()
invokes members from the base class Motor
. If you try inserting the following in main()
:
myDreamCar.PumpFuel(); // cannot access base's public member
it fails compilation with an error similar to error C2247: Motor::PumpFuel not accessible
because 'Car' uses 'private' to inherit from 'Motor.'
Note
If another class RaceCar
had to inherit from Car
, then irrespective of the nature of inheritance between RaceCar
and Car, RaceCar
would not have access to any public member or function of base class Motor
. This is because the relationship between Car
and Motor
is one of private
inheritance, meaning that all entities other than Car
have private
access (that is, no access) to public
and protected
members of Base
when using an instance of Car
.
In other words, the most restrictive access specifier takes dominance in the compiler’s calculation of whether one class should have access to a base class’s public
or protected
members.
Protected inheritance differs from public inheritance in that the keyword protected
is used in the line where the derived class declares its inheritance from a base class:
class Base
{
// ... base class members and methods
};
class Derived: protected Base // protected inheritance
{
// ... derived class members and methods
};
Protected inheritance is similar to private inheritance in the following ways:
It also signifies a has-a relationship.
It also lets the derived class access all public and protected members of Base
.
Those outside the inheritance hierarchy with an instance of Derived
cannot access public members of Base
.
Yet, protected inheritance is a bit different when it comes to the derived class being inherited from:
class Derived2: protected Derived
{
// can access public & protected members of Base
};
Protected inheritance hierarchy allows the subclass of the subclass (that is, Derived2
) access to public
and protected
members of the Base
as shown in Listing 10.9. This would not be possible if the inheritance between Derived
and Base
were private
.
0: #include <iostream>
1: using namespace std;
2:
3: class Motor
4: {
5: public:
6: void SwitchIgnition()
7: {
8: cout << "Ignition ON" << endl;
9: }
10: void PumpFuel()
11: {
12: cout << "Fuel in cylinders" << endl;
13: }
14: void FireCylinders()
15: {
16: cout << "Vroooom" << endl;
17: }
18: };
19:
20: class Car:protected Motor
21: {
22: public:
23: void Move()
24: {
25: SwitchIgnition();
26: PumpFuel();
27: FireCylinders();
28: }
29: };
30:
31: class RaceCar:protected Car
32: {
33: public:
34: void Move()
35: {
36: SwitchIgnition(); // RaceCar has access to members of
37: PumpFuel(); // base Motor due to "protected" inheritance
38: FireCylinders(); // between RaceCar & Car, Car & Motor
39: FireCylinders();
40: FireCylinders();
41: }
42: };
43:
44: int main()
45: {
46: RaceCar myDreamCar;
47: myDreamCar.Move();
48:
49: return 0;
50: }
Output
Ignition ON
Fuel in cylinders
Vroooom
Vroooom
Vroooom
Analysis
class Car
inherits using protected
from Motor
as shown in Line 20. class RaceCar
inherits using protected
from class Car
using protected as shown in Line 31. As you can see, the implementation of RaceCar::Move()
uses public methods defined in base class Motor
. This access to the ultimate base class Motor
via intermediate base class Car
is governed by the relationship between Car
and Motor
. If this were private
instead of protected
, SuperClass
would have no access to the public members of Motor
as the compiler would choose the most restrictive of the relevant access specifiers. Note that the nature of the relationship between the classes Car
and RaceCar
plays no role in access to base Motor
, while the relationship between Car
and Motor
does. So, even if you change protected
in Line 31 to public
or to private
, the fate of compilation of this program remains unchanged.
Caution
Use private
or protected
inheritance only when you have to. In most cases where private inheritance is used, such as that of the Car and the Motor, the base class could have as well been a member attribute of the class Car
instead of being a super-class. By inheriting from class Motor
, you have essentially restricted your Car to having only one motor, for no significant gain over having an instance of class Motor
as a private
member.
Cars have evolved, and hybrid cars, for instance, have a gas motor in addition to an electric one. Our inheritance hierarchy for class Car
would prove to be a bottleneck in being compatible to such developments.
Note
Having an instance of Motor
as a private
member instead of inheriting from it is called composition or aggregation. Such a class Car
looks like this:
class Car
{
private:
Motor heartOfCar;
public:
void Move()
{
heartOfCar.SwitchIgnition();
heartOfCar.PumpFuel();
heartOfCar.FireCylinders();
}
};
This can be good design as it enables you to easily add more motors as member attributes to an existing Car class without changing its inheritance hierarchy or its design with respect to its clients.
Derived objDerived;
Base objectBase = objDerived;
Or, alternatively, what if a programmer does this?
void UseBase(Base input);
...
Derived objDerived;
UseBase(objDerived); // copy of objDerived will be sliced and sent
In both cases, an object of type Derived
is being copied into another of type Base
, either explicitly via assignment or by passing as an argument. What happens in these cases is that the compiler copies only the Base
part of objDerived
—that is, not the complete object. The information contained by the data members belonging to Derived
is lost in the process. This is not anticipated, and this unwanted reduction of that part of data that makes the Derived
a specialization of Base
is called slicing.
Caution
To avoid slicing problems, don’t pass parameters by value. Pass them as pointers to the base class or as a (optionally const
) reference to the same.
Earlier in this lesson I mentioned that in some certain cases multiple inheritance might be relevant, such as with the platypus. The platypus is part mammal, part bird, and part reptile. For such cases, C++ allows a class to derive from two or more base classes:
class Derived: access-specifier Base1, access-specifier Base2
{
// class members
};
The class diagram for a platypus, as illustrated by Figure 10.3, looks different from the previous ones for Tuna
and Carp
(refer to Figure 10.2).
Thus, the C++ representation of class Platypus
is the following:
class Platypus: public Mammal, public Reptile, public Bird
{
// ... platypus members
};
A manifestation of Platypus
that demonstrates multiple inheritance is demonstrated by Listing 10.10.
0: #include <iostream>
1: using namespace std;
2:
3: class Mammal
4: {
5: public:
6: void FeedBabyMilk()
7: {
8: cout << "Mammal: Baby says glug!" << endl;
9: }
10: };
11:
12: class Reptile
13: {
14: public:
15: void SpitVenom()
16: {
17: cout << "Reptile: Shoo enemy! Spits venom!" << endl;
18: }
19: };
20:
21: class Bird
22: {
23: public:
24: void LayEggs()
25: {
26: cout << "Bird: Laid my eggs, am lighter now!" << endl;
27: }
28: };
29:
30: class Platypus: public Mammal, public Bird, public Reptile
31: {
32: public:
33: void Swim()
34: {
35: cout << "Platypus: Voila, I can swim!" << endl;
36: }
37: };
38:
39: int main()
40: {
41: Platypus realFreak;
42: realFreak.LayEggs();
43: realFreak.FeedBabyMilk();
44: realFreak.SpitVenom();
45: realFreak.Swim();
46:
47: return 0;
48: }
Output
Bird: Laid my eggs, am lighter now!
Mammal: Baby says glug!
Reptile: Shoo enemy! Spits venom!
Platypus: Voila, I can swim!
Analysis
class Platypus
features a really compact definition in Lines 30–37. It essentially does nothing more than inherit from the three classes Mammal
, Reptile
, and Bird
. main()
in Lines 41–44 is able to invoke these three characteristics of the individual base classes using an object of the derived class Platypus
that is named realFreak
. In addition to invoking the functions inherited from classes Mammal
, Bird
, and Reptile
, main()
in Line 45 invokes Platypus::Swim()
. This program demonstrates the syntax of multiple inheritance and also how a derived class exposes all the public attributes (in this case public member functions) of its many base classes.
final
Starting with C++11, compilers support specifier final
. It is used to ensure that a class declared as final
cannot be used as a base class. In Listing 10.10 for instance, class Platypus
represents a well-evolved species. You may therefore want to ensure that this class is final
, thereby blocking every possibility to inherit from it. A version of class Platypus
taken from Listing 10.10 and declared as final
would look like this:
class Platypus final: public Mammal, public Bird, public Reptile
{
public:
void Swim()
{
cout << "Platypus: Voila, I can swim!" << endl;
}
};
In addition to classes, final
can also be used on member functions in controlling polymorphic behavior. This is discussed in Lesson 11, “Polymorphism.”
Note
Platypus can swim, but it’s not a fish. Hence, in Listing 10.10, you did not inherit Platypus
from Fish
just for the convenience of reusing an existing Fish::Swim()
function. When making design decisions, don’t forget that public inheritance also should signify an “is-a” relationship. It should not be used indiscriminately with the purpose of fulfilling goals related to code reuse. Those goals can still be achieved differently.
In this lesson, you learned the basics of inheritance in C++. You learned that public inheritance is an is-a relationship between the derived class and base class, whereas private
and protected
inheritances create has-a relationships. You saw the application of access specifier protected
in exposing attributes of a base class only to the derived class, but keeping them hidden from classes outside the inheritance hierarchy. You learned that protected
inheritance differs from private
in that the derived classes of the derived
class can access public
and protected
members of the base class, which is not possible in private inheritance. You learned the basics of overriding methods and hiding them and how to avoid unwanted method hiding via the using
keyword.
You are now ready to answer some questions and then continue to learning the next major pillar of object-oriented programming, polymorphism.
Q I have been asked to model class Mammal along with a few mammals such as the Human, Lion, and Whale. Should I use an inheritance hierarchy, and if so which one?
A As Human
, Lion
, and Whale
are all mammals and essentially fulfill an is-a relationship, you should use public inheritance where class Mammal
is the base class, and others such as class Human
, Lion
, and Whale
inherit from it.
Q What is the difference between the terms derived class and subclass?
A Essentially none. These are both used to imply a class that derives—that is, specializes—a base class.
Q A derived class uses public inheritance in relating to its base class. Can it access the base class’s private members?
A No. The compiler always ensures that the most restrictive of the applicable access specifiers is in force. Irrespective of the nature of inheritance, private
members of a class are never accessible outside the class. An exception to this rule applies to classes and functions that have been declared as a friend
.
The Workshop provides quiz questions to help you solidify your understanding of the material that was covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E, and be certain you understand the answers before continuing to the next lesson.
1. I want some base class members to be accessible to the derived class but not outside the class hierarchy. What access specifier do I use?
2. If I pass an object of the derived class as an argument to a function that takes a parameter of the base class by value, what happens?
3. Which one should I favor? Private inheritance or composition?
4. How does the using
keyword help me in an inheritance hierarchy?
5. A class Derived
inherits private
from class Base
. Another class SubDerived
inherits public
from class Derived
. Can SubDerived
access public members of class Base
?
1. In what order are the constructors invoked for class Platypus
as shown in Listing 10.10?
2. Show how a class Polygon
, class Triangle
, and class Shape
are related to each other.
3. class D2
inherits from class D1
, which inherits from class Base
. To keep D2
from accessing the public
members in Base
, what access specifier would you use and where would you use it?
4. What is the nature of inheritance with this code snippet?
class Derived: Base
{
// ... Derived members
};
5. BUG BUSTERS: What is the problem in this code:
class Derived: public Base
{
// ... Derived members
};
void SomeFunc (Base value)
{
// ...
}
18.219.246.211