6.3 Inheriting Operations and Data


In C++, the relationship between a base and derived classes is represented syntactically as follows:

       class DisplayableNumber {...};                  // base class
        class Number : public DisplayableNumber {...}; // derived class
        class Cycler  : public DisplayableNumber {...}; // derived class

The additional syntax ": public DisplayableNumber" is used in the definition of each derived class to name the base class from which the derived class inherits.

The keyword public in this context means that all of the public methods defined in the base class (DisplayableNumber) become part of the public interface of the derived class (Number, Cycler). These inherited methods represent the code sharing that is achieved by inheritance. Alternatives to the use of the public form of inheritance are described later in this chapter.

The public interface of the classes DisplayableNumber, Number, and Cycler are shown below. The same basic class structure is used to define both a base and a derived class. Both base and derived classes have constructors and destructors: the only difference between a base and a derived class is their roles in the ineritance relationship that relates them to each other.


Syntax of Inheritance
class DisplayableNumber {
   public:
     DisplayableNumber(int init = 0);  // initial value zero by default
     void ShowIn(TextBox& p);
     void Show();
     void Reset();
     int  Value();
    ~DisplayableNumber();
};

class Number : public DisplayableNumber {
   public:
     Number(int init = 0);
     void Next();
    ~Number();
};

class Cycler : public DisplayableNumber {
   public:
     Cycler(int b, int init = 0);
     void Next();
     ~Cycler();
};

       

Notice that the base class DisplayableNumber forms a complete class - it defines an unchanging integer that can be displayed in a TextBox. In some limited cases this may be all that is needed. For example, a portion of an interactive testing system may need to display a final score to the user as follows:

       TextBox scoreBox(Location(100,100), Shape(75, 50));
        int score;
        
        // interact with user; determine final score

        DisplayableNumber finalScore(score);
        finalScore.ShowIn(scoreBox);
        finalScore.Show();

The classes derived from DisplayableNumber introduce the ability to change the number dynamically. Each new derived class embodies a different way to change the value.

The critical point is that, through inheritance, Number and Cycler objects have not only those methods defined in the Number and Cycler classes, but also those methods inherited from the public interface of the base class, DisplayableNumber. For example:

       Number count(10);      // initially 10
        Cycler  binary(2);      // base 2

        TextBox display(Location(10,10), Shape(50,50));
        TextBox onoff  (Location(20,20), Shape(50,50));

        count.ShowIn(display);
        binary.ShowIn(onoff);

        count.Next();           // increment by 1
        binary.Next();          // increment by 1 modulo 2

        count.Show();           // display updated value
        binary.Show();          // display updated value

        int c = count.Value();  // get value of Number object
        int b = binary.Value(); // get value of Cycler object

As this example illustrates, the Number and Cycler objects can respond both to the methods defined in their immediate classes (Next) and to the inherited methods (ShowIn, Show, Reset and Value) that are defined in the base class DisplayableNumber.

The Protected Section

Data can also be placed in the base class. In the example above, the value and textBox variables are common to both the Number and Cycler classes. This data should be promoted to the base class. The variable textBox is used only in the methods ShowIn, Show, and Reset, all of which are in the base class (DisplayableNumber). Thus, the textBox variable can be placed in the private data area of the base class. The variable value, however, is needed in both the base class, where it is used by the Show and Reset methods, and in the derived class, where it is used in the Next method.

Data to be shared among the base and derived classes is placed in a new region, the protected section, in the base class. Data placed in this new section is accessible to the base class and to the derived classes. Similar to the private: and public: sections, the new section is introduced by the protected: keyword. The class definition, including the private and protected data, for the DisplayableNumber base class is below.


Example of the Protected Section
class DisplayableNumber {
   private:
     TextBox* textBox;                 // place to display value

   protected:          // the following are accessible in derived classes
     int      value;                   // internal counter 

   public:
          DisplayableNumber(int init);  // initial value 
     void ShowIn(TextBox& p);
     void Show();
     void Reset();
     int  Value();                     // reply current value
         ~DisplayableNumber();
   };


The protected section is needed because neither the private nor public sections are adequate places to put the shared data. If the shared data is placed in the private section of the base class, it is inaccessible to the derived classes. This is, of course, contrary to what is needed. If the shared data is placed in the public section of the base class, then although it is accessible to the derived classes, it is also accessible as public data in the interface of the derived class, which means that the data loses its encapsulation and is exposed to the user of the objects created from the derived class. This is also contrary to what is required.

The protected section contains data (and operations) that are accessible to the base class, and derived class, but inaccessible everywhere else. In other words, protected data (and operations) are not part of the public interface of either the base class or the derived classes.

Even though the protected data is not part of the public interface, many designers argue that the data should remain in the private section and accessor methods should be placed in the protected section. This way, the data remains the private concern of the class in which it is declared and can still be accessed by the derived classes. Using this approach the DisplayableNumber class would be written as show below.


Using Protected Accessor Methods
class DisplayableNumber {
   private:
     TextBox* textBox;                 // place to display value
     int      value;                   // internal counter 
  protected:          // the following accessor methods 
                       // are visible in derived classes

     void setValue(int v);  // sets "value"
     int  getValue();       // gets "value"
    

   public:
          // ... as before
   };

Because the accessor methods are in the protected section, they are not part of the public interface of the base or any derived class. The accessor methods are, however, visible to the derived classes. Thus, the Number's Next method would be written as follows:

     void Number::Next() { setValue( getValue() + 1); }

This example also illustrates why some developers argue against the accessor method approach: as shown here, two method invocations are necessary to simply increment the value of the Number by one. This overhead may be disproportionate to the additional encapsulation gained by the accessor-method approach.

Constructors

The constructors for derived classes, like those for the the Number and Cycler classes, must be related to the constructors of their base classes, like DisplayableNumber. The constructor for the Number class has an integer argument that should be used to initialize the Number's internal value. However, the DisplayableNumber class also has a constructor for that purpose. In this case, the Number's constructor argument should simply be passed on to the DisplayableNumber's constructor. The C++ syntax for this is:

       Number::Number(int init) : DisplayableNumber(init) { }

The additional syntax ": DisplayableNumber(init) " means that the derived class constructor argument (init) is used as the base class constructor argument. In this case, no other initialization is done in the derived class (the body of the Number's constructor has no code).

The Cycler class illustrates a different relationship between the constructors of the derived and base classes. The first integer value for the Cycler's constructor is the base of the Cycler and the second integer value is the initial value of the Cycler. This is reflected in the Cycler's constructor as follows:

       
          Cycler::Cycler(int b, int init) : DisplayableNumber(init)
          { base = b; }

In this case some of the derived class's constructor arguments are used to construct the base class while others are used to initialize the data in the derived class itself.

The constructors of the base and derived classes are executed in a prescribed order: the base class is constructed first, then the class immediately derived from the base class, etc. The order in which the constructors are executed is illustrated by the code shown below. This code uses trivialized classes that have the same inheritance relationships and the same constructors as the real classes for which they are named. The output from the main program clearly shows that the base class constructor is the first one to be executed, followed by the constructor of its derived class.


Order of Constructor Execution
class DisplayableNumber 
{ private: int value; 
  public: DisplayableNumber(int init); 
}; 

class Cycler : public DisplayableNumber 
{ private: int base; 
  public: Cycler(int b, int init = 10); 
}; 

DisplayableNumber::DisplayableNumber(int init) 
{ cout << "DisplayableNumber Constructor " << init << endl; } 

Cycler::Cycler(int b, int init) : DisplayableNumber(init) 
{base = b; cout << "Cycler Constructor " << b << endl; } 

void main() 
{ Cycler cycler(8, 20);} 

The output from this program is

    DisplayableNumber Constructor 20
     Cycler Constructor 8

which clearly shows that the DisplayableNumber base class constructor is executed before the constructor of the derived Cycler class. It also confirms that the second argument of the Cycler's constructor (20) has been passed from from the Cycler constructor to the DisplayableNumber constructor.

The execution of the constructors guarantees a derived class that the base class layers have been properly initialized before the derived class constructor is executed. This is useful in those cases where derived classes use the data or methods of the base class to perform their own initialization.

In summary, the constructor arguments are distributed from the bottom up (i.e., proceeding from the most derived class to the base class) and then the constructors are executed top down (i.e., proceeding from the base class to the most derived class).

A Complete Derived Class

Having declared the protected data in the base class, the code for the derived classes is:

       class Number : public DisplayableNumber {
        public:
          Number(int init = 0);
          void Next();
         ~Number();
        };

        class Cycler : public DisplayableNumber {
        private:
          int base;
        public:
          Cycler(int b, int init = 0);
          void Next();
         ~Cycler();
        };

and the code for the methods of these classes is:

       Number::Number(int init) : DisplayableNumber(init) { }
        void Number::Next() { value = value + 1; }
        Number::~Number() {}


        Cycler::Cycler(int b, int init) : DisplayableNumber(init) { base = b; }
        void Cycler::Next() { value = (value + 1)%base; }
        Cycler::~Cycler() {}

  1. Implement and test the DisplayableNumber base class, the Number derived class, and the Cycler derived class.

  2. Implement and test the DownCounter class. Your test program should show a DownCounter object's value in a TextBox and have a button labeled Next which, when pressed, causes the object's Next operation to be executed.

  3. Implement and test the UpDownCounter class. Your test program should show an UpDownCounter object's value in a TextBox and have buttons labelled Next and Previous which , when pressed, cause the object's Next and Previous operations, respectively, to be executed.

  4. Implement and test the BiCyler class. Your test program should show a BiCyler object's value in a TextBox and have buttons labelled Next and Previous which , when pressed, cause the object's Next and Previous operations, respectively, to be executed.

  5. Implement and test the SwitchCounter class. Your test program should show a SwitchCounter object's value in a TextBox and have buttons labeled Next and Switch which , when pressed, cause the object's Next and Switch operations, respectively, to be executed.

  6. Implement and test the BatchCounter class. Your test program should show a BatchCounter object's value in a TextBox and have a button labelled Next which, when pressed, causes the object's Next operation to be executed. .

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement

 

 

 

ÿ