Lesson 10. Implementing Inheritance

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

Image Inheritance in the context of programming

Image The C++ syntax of inheritance

Image public, private, and protected inheritance

Image Multiple inheritance

Image Problems caused by hiding base class methods and slicing

Basics of Inheritance

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.

Image

FIGURE 10.1 Inheritance between classes.

Inheritance and Derivation

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.

Image

TABLE 10.1 Examples of Public Inheritance Taken from Daily Life

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.

Image

FIGURE 10.2 Hierarchical relationship between Tuna, Carp, and Fish.

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.

C++ Syntax of Derivation

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.

LISTING 10.1 A Simple Inheritance Hierarchy Demonstrated by the Piscean World


  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 Image

About my food:
Lunch: Swims in lake
Dinner: Swims in sea

Analysis Image

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

Access Specifier Keyword 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.

LISTING 10.2 A Better class Fish Using the protected Keyword to Expose Its Member Attribute Only to the Derived Classes


  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 Image

About my food
Lunch: Swims in lake
Dinner: Swims in sea

Analysis Image

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.

Base Class Initialization—Passing Parameters to the Base Class

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.

LISTING 10.3 Derived Class Constructor with Initialization Lists


  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 Image

About my food
Lunch: Swims in lake
Dinner: Swims in sea

Analysis Image

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.


Derived Class Overriding Base Class’s Methods

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.

LISTING 10.4 Derived Classes Tuna and Carp Overriding Method Swim() in Base Class Fish


  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 Image

About my food
Lunch: Carp swims real slow
Dinner: Tuna swims real fast

Analysis Image

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.

Invoking Overridden Methods of a Base Class

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.

Invoking Methods of a Base Class in a 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.

LISTING 10.5 Using Scope Resolution Operator (::) to Invoke Base Class Functions from Derived Class and main()


  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 Image

About my food
Lunch: Carp swims real slow
Swims in lake
Dinner: Swims in sea

Analysis Image

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.

Derived Class Hiding Base Class’s Methods

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.

LISTING 10.6 Tuna::Swim() Hides Overloaded Method Fish::Swim(bool)


  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 Image

About my food
Tuna swims real fast

Analysis Image

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:

Image Solution  1: Use the scope resolution operator in main():

myDinner.Fish::Swim();

Image 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;
   }
};

Image 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;
   }
};

Order of Construction

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

Order of Destruction

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.

LISTING 10.7 The Order of Construction and Destruction of the Base Class, Derived Class, and Members Thereof


  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 Image

FishDummyMember constructor
Fish constructor
TunaDummyMember constructor
Tuna constructor
Tuna destructor
TunaDummyMember destructor
Fish destructor
FishDummyMember destructor

Analysis Image

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

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).

Image

TABLE 10.2 Examples of Private Inheritance Taken from Daily Life

Let’s visualize private inheritance in a car’s relationship to its motor. See Listing 10.8.

LISTING 10.8 A class Car Related to class Motor via private Inheritance


  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 Image

Ignition ON
Fuel in cylinders
Vroooom

Analysis Image

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

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:

Image It also signifies a has-a relationship.

Image It also lets the derived class access all public and protected members of Base.

Image 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.

LISTING 10.9 class RaceCar That Derives from class Car That Derives from class Motor Using protected Inheritance


  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 Image

Ignition ON
Fuel in cylinders
Vroooom
Vroooom
Vroooom

Analysis Image

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.


The Problem of Slicing

What happens when a programmer does the following?

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.


Multiple Inheritance

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).

Image

FIGURE 10.3 Relationship of a class Platypus, to classes Mammal, Reptile, and Bird.

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.

LISTING 10.10 Using Multiple Inheritance to Model a Platypus That Is Part Mammal, Part Bird, and Part Reptile


  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 Image

Bird: Laid my eggs, am lighter now!
Mammal: Baby says glug!
Reptile: Shoo enemy! Spits venom!
Platypus: Voila, I can swim!

Analysis Image

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.

Avoiding Inheritance Using 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.


Summary

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&A

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.

Workshop

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.

Quiz

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?

Exercises

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)
{
   // ...
}

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

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