Reusing Classes

(inheritance)



Section Contents

Topics should be read in order presented.

After clicking on a topic below, press "BACK" on your browser to return to Section Contents. Or, instead of jumping to topics and returning back to Section Contents, begin, rather, with the topic introduction to inheritance as it appears immediately following Section Contents, and then advance sequentially through the remainder of the topics.



introduction to inheritance:

In the demonstration of a complete class put to full use (by means of "messages" sent to class Pet through a class Pet object) in the form of a C++ program (which appeared near the end of the previous section on classes), class Pet is the only class in the entire program. In the majority of C++ programs, however, a program consists of many classes. These classes are determined by the programmer, based upon the requirements and needs of the program being written. One of the most fundamental aspects of programming in C++ is in fact determining what classes to use in a program. This brings us to yet another powerful feature of object-oriented programming: inheritance. Inheritance is the second (of the three) features of object-oriented programming we will be studying that make object-oriented programming ("OOP") what it is. The concept of object-oriented programming, as you may recall, was discussed in detail in the previous section on classes. What is "inheritance"? When deciding upon what classes are to be considered part of a program, sometimes we find that some classes are, in fact, more specific examples of a more generalized concept. In order to better understand things here, refer to the graphical portrayal of the "inheritance hierarchy" displayed below:

           Pet
            | 
          / | \
         /  |  \
        /   |   \
       /    |    \
      /     |     \
    Dog    Cat   Bird

class Pet is called a base class, in the sense that it is at the "base" of the inheritance hierarchy. class Dog, class Cat, and class Bird are all called derived classes - they are, quite simply, "derived" from class Pet. These 3 derived classes "inherit" from class Pet. Because class Dog, class Cat, and class Bird are all types of pets, they "inherit" the behaviors and properties (member functions and data members) of class Pet. What behavior or property could a pet possess, do you suppose, that each of these 3 types of pets would not also possess? A "derived" class, once again, is a more specific example of a more generalized "base" class. Why use "inheritance"?

The use of classes is what makes inheritance possible. classes possess behaviors (member functions) and properties (data members). These features allow a class to mimic real-world, daily-life entities. Just as a dog, cat, or bird is a type of a pet, through inheritance class Dog, class Cat, and class Bird all receive everything that makes class Pet a pet. Inheritance allows one to reuse existing classes. For example, class Dog, class Cat, and class Bird all reuse the member functions and data members of class Pet. Furthermore, any existing class written by a programmer has the potential to become a base class, from which further classes are derived. The use of classes along with inheritance makes possible a degree of efficiency and performance not possible with the traditional manner of programming.

This brings us back to what we were introduced to in the previous section on classes as a "software brick". In review: a "software brick" is a completely self-contained collection of information. In other words, the term "software brick" is in reality another way of referring to what we have come to know as a class. One important aspect of classes that contributes to the usage of the term "software brick" is that of encapsulation: grouping data members of a class with the member functions that act upon them. What makes the use of classes of importance is that inheritance is not possible unless classes are first established.

It so happens that the concept of a "software brick" also applies to the use of inheritance. When a programmer creates a class, he makes sure that the source code contained within the class is completely debugged, and totally reliable. The code has been thoroughly tested and retested. Therefore, when another programmer uses such a class as a base class, that programmer need not concern himself as to whether or not the base class is free of error. This "software brick" will fit nicely into whatever purpose the programmer has for the class he has derived from the base class.


inheritance hierarchy class definitions:

Below are the class definitions corresponding to the class Pet inheritance hierarchy displayed above. Make note, firstly, within the definition of class Pet below, that class Pet has been modified a bit from how it appears in the previous section on classes: the task member function PerformTrick( ) has been replaced with a task member function we are calling GreetBoy( ). As you can see by examining the class definitions below, furthermore, GreetBoy( ) also appears in class Dog and class Cat. The importance of this will be discussed shortly. Also new to class Pet is the data member pet_greeting. class Dog contains a member function we are calling ChewBone( ), and class Bird contains a member function we are calling EatSeeds( ). Also appearing in class Dog and class Bird are the data members lunch_food and pet_is_hungry.

class Pet
{
   public:
      Pet();
      void GreetBoy();  // base class function.
      ~Pet();

   protected:
      char *pet_greeting; // base class data member.
};

class Dog : public Pet   // Dog inherits Pet.
{
   public:
      Dog();
      void GreetBoy(); // base class function redefined.
      void ChewBone(); // new class Dog function.
      ~Dog();

   private:
      char *lunch_food;   // new class Dog data
      bool pet_is_hungry; // members.
};

class Cat : public Pet   // Cat inherits Pet.
{
   public:
      Cat();
      void GreetBoy();  // base class function redefined.
      ~Cat();
};

class Bird : public Pet // Bird inherits Pet.
{
   public:
      Bird();
      void EatSeeds();  // new class Bird function.
      ~Bird();

   private:
      char *lunch_food;    // new class Bird data
      bool pet_is_hungry;  // members.
};

specifying inheritance:

Let us discuss the above class definitions. The first new addition you may come across is the notation ' : public Pet' following the class names of class Dog, class Cat, and class Bird in their class definitions. For example, consider the statement class Dog : public Pet. In a nutshell, this statement tells us that class Pet is the base class, and class Dog is the class derived from class Pet; class Dog inherits from class Pet. Everything that belongs to class Pet now also belongs to class Dog.


the protected keyword:

The next new addition you may have come across in the above class definitions is the protected keyword in the class definition of class Pet. As you may recall, if the data members of a class are declared 'private', those data members cannot be accessed from outside of that class (just as a class object cannot access private data members). Unfortunately, this aspect of the 'private' keyword also applies to classes derived from a base class: if the data members of a base class are declared private, any classes derived from that base class cannot inherit those data members - the derived classes would be treated as if they were a class object attempting to reach a private data member from outside of that class.

The protected keyword is the answer to this. When the data members of a base class are declared protected, you see, classes derived from that base class are given complete access to those otherwise inaccessible data members. Quite simply, the 'protected' keyword makes possible data members of a base class to be accessed by all of its derived classes. Take note, however, that the same "firewall" concept that applies to the private keyword still applies to data members declared protected: as with the private keyword, class data members declared protected in a base class cannot be accessed from outside of that class; to access a class "from the outside", in review, means to communicate with a class through an object of that class. All data members declared 'protected' in a base class, finally, become of 'protected' status in all classes derived from that base class.

Furthermore: as we learned in the previous section on classes, a utility member function (a function within a class that performs some sort of task related to the inner, hidden details of the class) is, as a rule, most usually defined as private. However, there will be times in which the creator of a base class intends on classes derived from that base class to be able to inherit a utility member function appearing in the base class. This does not go far beyond what we've just finished discussing. As we already know, a utility member function defined as private in a base class cannot be inherited by classes derived from that base class. To make the utility member function appearing in the base class able to be inherited by classes derived from the base class, the creator of the base class need only define the utility member function as protected. Upon doing so, all classes derived from the base class have complete access to the base class utility member function.


object messages and inheritance:

As can be seen in the above class definitions, class Pet (the base class) possesses one member function - GreetBoy( ), and one data member - pet_greeting. When class Pet in inherited by one of the 3 derived classes, there exists three possibilities concerning member functions that lie in the hands of the creator of that derived class: the first option is for the derived class to simply inherit a member function appearing in class Pet (receive the base class member function as it is): the base class member function automatically becomes part of the derived class. The second option is for the derived class to redefine a member function appearing in class Pet (provide a new version of the member function). The final option is for the derived class to introduce new member functions into the situation - member functions that are then considered unique to that derived class, not appearing in the base class. To better understand how inheritance in C++ works, let us examine the definition of each of the 3 derived classes displayed above more closely.

In class Dog, GreetBoy( ) is redefined. A new member function - ChewBone( ) - is newly-introduced. Also newly-introduced are the data members 'lunch_food' and 'pet_is_hungry'.

In class Cat, GreetBoy( ) is also redefined. However, class Cat declares no newly-introduced member functions or data members.

In class Bird, GreetBoy( ) is not redefined, but is rather inherited. However, a new member function - EatSeeds( ) - is newly-introduced. Newly-introduced data members 'lunch_food' and 'pet_is_hungry' are declared.

It is now time to discuss the basic concepts behind how classes in an inheritance hierarchy work, by making the classes part of an up and running C++ program. These details may seem confusing at first, but once fully understood, allow one to apply the practice of inheritance to its full use. Displayed below is a program portraying the use of base class (as well as derived class) objects to send class messages:

#include "pet.h"
#include "dog.h"
#include "cat.h"
#include "bird.h"

void main()
{
   Pet pet;       // Pet object.
   Dog dog;       // Dog object.
   Cat cat;       // Cat object.
   Bird bird;     // Bird object.

   pet.GreetBoy();    // calls Pet::GreetBoy()
                     // [base class object; base class function]

   bird.GreetBoy();  // calls Pet::GreetBoy()
                    // [derived class object; function inherited]

   dog.GreetBoy();   // calls Dog::GreetBoy()
                    // [derived class object; function redefined]
 
   cat.GreetBoy();  // calls Cat::GreetBoy()
                   // [derived class object; function redefined]
   
   dog.ChewBone();  // calls Dog::ChewBone()
                   // [derived class object; newly-introduced function]

   bird.EatSeeds(); // calls Bird::EatSeeds()
                   // [derived class object; newly-introduced function]
}

As with #include "pet.h" in the previous section, the presence of the #include statements above main( ) prepare the program to be able to use class Pet and its derived classes.


the 4 types of object messages:

Having studied the possibilities of inheritance-based object messages as they appear in program code, it is now time that we proceed more deeply into this topic by discussing the 4 types of inheritance-based object messages.


base class object message to a base class member function:

The first type of object message is the simplest of the 4 types of object messages: the use of a base class (class Pet) object to access a member function declared in that base class. The example portraying this in the above program is pet.GreetBoy( ). The base class version of GreetBoy( ) is called.


derived class object message to a base class member function not redefined (but rather inherited):

The second type of object message involves a base class member function appearing in a base class not redefined in a derived class. The message bird.GreetBoy( ) is an example of this: GreetBoy( ) appears in class Pet, but is not redefined in class Bird. class Bird simply inherits the class Pet version of GreetBoy( ). Given that class Bird (the derived class) has inherited the class Pet version of GreetBoy( ), therefore, the inherited function is accessed through an object of the derived class. Since the class Pet version of GreetBoy( ), through inheritance, now belongs to class Bird, quite clearly, the call to GreetBoy( ) through the class Bird object results in the class Pet (base class) version of GreetBoy( ) being called.


derived class object message to a redefined base class member function:

The third type of object message involves a base class member function appearing in a base class that is redefined in a derived class. The messages dog.GreetBoy( ) and cat.GreetBoy( ) are examples of this: GreetBoy( ) appears in class Pet (the base class), and is redefined in class Dog and class Cat. The class Dog and class Cat versions of GreetBoy( ) "overwrite", so to speak, the class Pet version of GreetBoy( ). Given that class Dog and class Cat (the derived classes) possess their own versions of GreetBoy( ), therefore, the redefined version of GreetBoy( ) can be accessed through an object of that derived class. Since class Dog and class Cat now possess their own versions of GreetBoy( ) (by redefining the class Pet version of GreetBoy( ) ), a call to GreetBoy( ) through a derived class object results in the derived class version of GreetBoy( ) being called. Worth mentioning here is that a primary reason that the derived class version of GreetBoy( ) is chosen in a class object message such as dog.GreetBoy( ) (rather than the base class version of GreetBoy( ) ) is, quite clearly, because the message itself is being performed through a derived class object.


derived class object message to a newly-introduced member function:

The fourth and final type of object message involves a member function newly-introduced in a derived class. Such a "newly-introduced" function is accessed through an object of the class within which the newly-introduced function appears. The messages dog.ChewBone( ) and bird.EatSeeds( ) are examples of this: class Dog and class Bird each contain a newly-introduced function. Newly-introduced functions are written by the creator of a derived class for the purpose of being used by that particular class. ChewBone( ) and EatSeeds( ) being newly-introduced functions, ChewBone( ) and EatSeeds( ) do not appear in class Pet. Rather, newly-introduced functions are accessed through objects of the classes that contain the newly-introduced functions.


On the topic of inheritance: as we've learned, if a programmer deriving a class from a base class chooses not to redefine a public member function appearing in that base class (chooses not to provide a new version of the function), the derived class will simply inherit the function (receive the base class member function as it is): the base class member function automatically becomes part of the derived class. Given this, what do we need to know concerning the act of a derived class inheriting the public member functions of a base class? With the type of inheritance we will be using (which will be discussed next), all member functions declared public in a base class are also public if inherited by a derived class. (This reasoning does not apply to utility member functions, however, because, as you may recall, utility member functions are most usually not declared public.)


inheriting data members:

So that we can better understand this topic, let us return to the inheritance hierarchy class definitions displayed earlier. Presented below, once again, is the complete class definition of class Pet:

class Pet
{
   public:
      Pet();
      void GreetBoy();  // base class function.
      ~Pet();

   protected:
      char *pet_greeting; // base class data member.
};

As you can see in the definition of class Pet shown here, class Pet possesses one data member: pet_greeting. This is no surprise, given that one of the primary purposes of a class definition is to declare the data members of the class. Upon examining the inheritance hierarchy class definitions displayed earlier, however, you will find that pet_greeting does not appear in the class definition of any of the 3 classes derived from class Pet. This is so, you see, because there is no need to declare pet_greeting in the 3 classes derived from class Pet: quite clearly, each of the 3 derived classes simply inherit the data member pet_greeting from class Pet. As you may recall from past discussion, this act of inheritance would not be possible if pet_greeting had been declared private in class Pet: as you may recall, everything declared private in a base class is inaccessible to classes derived from that base class. Because pet_greeting is declared protected in class Pet, however, all of class Pet's derived classes have complete access to pet_greeting.

Let us continue. When a derived class inherits a data member declared protected in a base class, the name of the received data member remains the same in the derived class. However, on the other hand, the derived class still receives its own unique copy of the data member: that is, the derived class possesses its own version of the data member, separate and apart from the base class version of the data member. The derived class having inherited pet_greeting, what the derived class does with pet_greeting is completely up to the programmer who created the derived class. Next and finally: data members declared as protected in a base class become protected in a derived class.

What, one may ask, if the creator of a derived class chooses to redefine a protected base class data member (declare a data member possessing the same name as a protected data member)? This would not be a problem: the derived class would simply use that data member as if it were inherited.

Return, if you will, to the topic presented earlier on the 4 types of inheritance-based object messages. As you may recall, we learned that it is possible for the creator of a derived class to write newly-introduced member functions for the derived class - functions unique to that derived class, not appearing in the base class. Two such member functions exist in the list of class definitions displayed earlier: ChewBone( ) (in class Dog) and EatSeeds( ) (in class Bird). Just as member functions can be newly-introduced, the same also holds true for data members. To better understand this it must be emphasized that inheritance from a base class is not the only means by which a derived class can obtain data members: as with member functions, data members can be newly-introduced. Examples of newly-introduced data members can be seen, once again, in the definitions of class Dog and class Bird displayed earlier: in the definition of each class, the data members 'lunch_food' and 'pet_is_hungry' are added.

Given what we've learned so far on the topic of inheriting data members, observe, if you will, the usage of the data member 'pet_greeting' in the member functions of class Dog, class Cat, and class Bird in the inheritance hierarchy source code displayed below. Each of these three classes do not declare 'pet_greeting' as part of their class definition: as stated, they simply inherit the protected data member pet_greeting from class Pet. As you can see by examining the source code displayed below, furthermore, each of these three derived classes treat the inherited data member pet_greeting as if pet_greeting had actually been declared as part of their own class definition.


inheritance hierarchy source code:

Displayed below is the class source code providing the bodies of the member functions appearing in the inheritance hierarchy class definitions shown earlier. The code should not require too much effort to follow in the sense that it does not go beyond anything we've already covered. Therefore, the code should be self-explanatory. Worth mentioning in the source code below is that no function body is provided for the class Bird version of GreetBoy( ). This is no mistake; given what we've learned so far, understanding why this is so should not be too difficult. Quite clearly, the reason that no GreetBoy( ) function body is provided for class Bird in the source code is, as you may recall, because class Bird does not redefine the class Pet version of GreetBoy( ) (does not provide a new version of the function).

Let us continue. Since class Bird does not redefine the class Pet version of GreetBoy( ), class Bird simply inherits the class Pet version of GreetBoy( ) (receives the function as it is): the class Pet version of GreetBoy( ) now belongs to class Bird. The class Pet version of GreetBoy( ), therefore, in a manner of speaking, automatically becomes part of class Bird. Because the class Pet version of GreetBoy( ) now belongs to class Bird, quite clearly, it is not hard to realize the fact that class Pet and class Bird possess identical versions of GreetBoy( ). Therefore, now that class Pet and class Bird possess identical versions of GreetBoy( ), there is no need to provide a function body for the class Bird version of GreetBoy( ) in the source code below: when the time comes to call the class Bird version of GreetBoy( ), the program simply uses the function body provided for the class Pet version of GreetBoy( ).

Pet::Pet()
{
   int length;     // length of current string.
   
   // allocate memory for pet greeting.
   length = strlen("[Pet greeting!]");
   pet_greeting = new char[length + 1];
   strcpy(pet_greeting, "[Pet greeting!]");
}

void Pet::GreetBoy()
{
   cout << "Pet said " << pet_greeting
        << endl;
}

Pet::~Pet()
{
   delete [] pet_greeting;   // deallocate pet greeting.
}

Dog::Dog()
{
   int length;        // length of current string.
   
   // allocate memory for pet greeting.
   length = strlen("Bark!");
   pet_greeting = new char[length + 1];
   strcpy(pet_greeting, "Bark!");

   // allocate memory for pet food.
   length = strlen("bone");
   lunch_food = new char[length + 1];
   strcpy(lunch_food, "bone");

   pet_is_hungry = true;     // pet is hungry.
}

void Dog::GreetBoy()
{
   cout << "Dog said " << pet_greeting
        << endl;
}

void Dog::ChewBone()
{
   if (pet_is_hungry == true)
      cout << "Dog ate " << lunch_food
           << '.' << endl;
   else
   if (pet_is_hungry == false)
      cout << "Dog not hungry." << endl;
}

Dog::~Dog()
{
   delete [] pet_greeting;  // deallocate pet greeting.
   delete [] lunch_food;    // deallocate pet food.
}
 
Cat::Cat()
{
   int length;       // length of current string.
   
   // allocate memory for pet greeting.
   length = strlen("Meow!");
   pet_greeting = new char[length + 1];
   strcpy(pet_greeting, "Meow!");
}

void Cat::GreetBoy()
{
   cout << "Cat said " << pet_greeting
        << endl;
}

Cat::~Cat()
{
   delete [] pet_greeting;  // deallocate pet greeting.
}

Bird::Bird()
{
   int length;       // length of current string.
   
   // allocate memory for pet greeting.
   length = strlen("Chirp!");
   pet_greeting = new char[length + 1];
   strcpy(pet_greeting, "Chirp!");

   // allocate memory for pet food.
   length = strlen("seeds");
   lunch_food = new char[length + 1];
   strcpy(lunch_food, "seeds");

   pet_is_hungry = false;   // pet not hungry.
}

void Bird::EatSeeds()
{
   if (pet_is_hungry == true)
      cout << "Bird ate " << lunch_food
           << '.' << endl;
   else
   if (pet_is_hungry == false)
      cout << "Bird not hungry." << endl;
}

Bird::~Bird()
{
   delete [] pet_greeting;  // deallocate pet greeting.
   delete [] lunch_food;    // deallocate pet food.
}

function pointers:

Having been made familiar with pointer variables of all kinds of data types, it is time we are introduced to a new type of pointer: the function pointer. If you are new to such an idea, the concept of a function pointer may seem odd at first. Do not be alarmed, though: continue reading. Of what use, one may ask, and of what possible purpose, could a "pointer to a function" actually serve? What must be understood here is that function pointers are not real functions written by the programmer. Rather, function pointers are assigned, in program code, to the address of real, existing functions. What is meant by the "address" of a function? How could a function possess an address?

Things make better sense here given that functions in reality begin at a certain address in memory. A function is a contant pointer to itself - once established, it cannot change: it holds the address of its own location in memory. In addition: function pointers can be assigned to other function pointers.

Declaring a function pointer is not much different from writing a function prototype for a regular function. Consider the following function prototype: void Calc (int, int);. To write the function pointer equivalent to this function prototype, simply place the asterisk operator in front of the function name (as in *Calc), and then place this application of the asterisk operator within parentheses, as in (*Calc). Finally, to declare the function pointer equivalent of the function prototype, replace the function name of the regular function prototype with its function pointer equivalent, which brings us to the final end result: void (*Calc)(int, int);.

Why are the asterisk operator and parentheses required? Without them the function pointer declaration would look like this: void Calc (int, int);. If this function prototype were actually used to declare a function pointer, the result would not be the desired outcome: without the asterisk operator and parentheses, Calc becomes an actual function, rather than a function pointer. It is the programmer's responsibility to ensure that this does not happen.

Moving along: in order to successfully assign a function pointer to a real, exisiting function, the return value and parameter lists of both must match.

Just as it is possible to perform a function call to a real existing function, it is possible to call a function pointer in a similar fashion. The actual function called depends completely upon the written function to which the function had been previously assigned. As a means of better understanding how to perform a function call through a function pointer, consider the following piece of code:

void Add (int, int);     // function prototype.

void Add (int num1, int num2)
{
   cout << "Sum is "
        << num1 + num2
        << '.' << endl;
}

void main()
{
   void (*Calc)(int, int);  // function pointer
                           // declaration.

   (*Calc) = Add;   // function pointer assignment.

   (*Calc)(3, 5);     // call performed through
                      // function pointer.
}

OUTPUT:

Sum is 8.

The first line of code is the function prototype of the function Add( ) - a function that has a void return value and a parameter list of 2 int variables. Next is the function body of Add( ), in which the sum of the two received int variables is outputted. In main( ), the function pointer (*Calc) is declared. Worth mentioning here is that Add( ) and (*Calc) both possess matching return types and parameter lists, which is why the program will successfully compile. The next line of code is where the function pointer (*Calc) is assigned to the real written function Add( ). That the final line of code is grasped is of greatest importance. When we see the statement (*Calc)(3, 5), the function that in reality is being called (and to which the two int parameters are being passed) is in fact the function Add( ). Quite clearly, at the moment of the function call performed through (*Calc), Add( ) was the function to which (*Calc) had been previously assigned.

Observe, if you will, the assignment of *Calc to a real written function within the switch( ) statement in the program above. What must be understood here is that the parentheses and asterisk operator here are not actually required. However, it is what I call a good programming practice to include them. The primary reason in doing so is that it tells the reader that that the pointer (*Calc) is in fact a function pointer and not any other type of pointer.

Also observe the statement (*Calc) = Add. If the programmer wanted to, he could apply the ampersand operator to the statement, as (*Calc) = &Add. Although this is not actually necessary, it reminds the reader that all real written functions exist at a specific address in memory.


function pointer assignment:

Having learned how to assign a function pointer to a real written function, we can now study how to use a function pointer to portray the use of a menu-driven system. Examine, if you will, the program displayed below:

void Add (int, int);          //
void Subtract (int, int);     // function prototypes.
void Multiply (int, int);     // 

void Add (int num1, int num2)
{
   int sum;

   sum = num1 + num2;

   cout << "Sum is " << sum
        << '.' << endl;
}

void Subtract (int num1, int num2)
{
   int difference;

   difference = num1 - num2;

   cout << "Difference is "
        << difference
        << '.' << endl;
}

void Multiply (int num1, int num2)
{
   int product;

   product = num1 * num2;

   cout << "Product is "
        << product
        << '.' << endl;
}

void main()
{
   void (*Calc)(int, int);     // function pointer
                              // declaration.

   int method;              // calculation method.
   int num1, num2;         // variables used for math.

   cout << "Enter method choice:"   // math
        << endl;                    // method
   cout << " 1 - Add" << endl;      // menu.
   cout << " 2 - Subtract" << endl;
   cout << " 3 - Multiply" << endl;
   cin >> method;

   cout << "Enter 2 numbers:"  // user input
        << endl;               // prompt.
   cin >> num1 >> num2;

   switch (method)
   {
      case 1 : (*Calc) = Add;  // function pointer
               break;          // assignment.

      case 2 : (*Calc) = Subtract;  // function pointer
               break;               // assignment.

      case 3 : (*Calc) = Multiply;  // function pointer
               break;               // assignment.
   }

   (*Calc)(num1, num2);   // call performed through
                          // function pointer.
}

This program contains 3 real written functions: Add( ), Subtract( ), and Multiply( ). All three functions have a void return value and receive 2 int parameters. The bodies of the three functions are quite similar: firstly, a variable is declared to hold the result of the desired mathematical calculation; then, the calculation is performed upon the 2 received parameters; finally, the end result is displayed. This brings us to main( ).

The first line of code within main( ) is the declaration of the function pointer (*Calc). The purpose of (*Calc) ("calculation"), quite clearly, is to generically process functions of similar purpose - these functions being Add( ), Subtract( ), and Multiply( ). Next, in main( ), after the function pointer (*Calc) is declared, a variable - method - is declared, whose purpose is to input, from the user, the method of mathematics he wishes for the program to perform. Afterward, 2 variables are declared, for the purpose of receiving, from the user, the values of the numbers to be involved in the actual mathematical calculation. Then, a menu consisting of 3 choices is presented, from which the user decides upon the method of mathematics to be applied. After the menu, the user is prompted to provide the 2 numbers to be used in the calculation, and then the two numbers are inputted.

This brings us to the switch( ) statement. Understanding this part of the program is vital to grasping the program as a whole. The switch( ) statement, firstly, takes the variable method as an argument - method being a number from 1 to 3 (the user's response to the menu that asks the user for the method of mathematics to be applied). The 'case' option chosen is dependent upon the value held by method. Upon the correct 'case' option having been chosen, (*Calc) is assigned to the appropriate real written function.

We cannot fully comprehend the purpose of the switch( ) statement until we have been introduced to the final line of code in main( ) - the place in the program where the actual function call takes place: (*Calc)(num1, num2). The purpose of the switch( ) statement, in a nutshell, is to PAIR (*Calc) with the function appropriate to the situation. Quite clearly, the particular function that this statement calls is ultimately dependent upon the function that (*Calc) had been assigned to within the switch( ) statement. What must be clearly understood here is that this function call cannot take place, you see, until the program knows which function it is that the program should call.

In conclusion: having learned how to use a function pointer to create a menu-driven system, we are ready to proceed onward to the next topic on function binding.


function binding:

In review: (*Calc) is not an actual existing function: it is a function pointer that takes on the form of the real written function it has been assigned to. In this sense, the address of the function to which a function pointer is assigned becomes the address held by the function pointer.

This brings us to the term binding. Let us discuss what binding has to do with our current topic of discussion. Binding is the process by which a computer converts the bits and pieces of a program (which includes functions) into sets of computer instructions and most importantly, afterward, into actual memory addresses. When a program is being compiled (converted into instructions a computer can understand) and the compiler encounters a call to a regular written function (such as Add( ) ) in program code, the compiler has no problem in associating the function call with the body of that function. The function can be called as it is. This is called early (or static) binding.

Let us discuss late (or dynamic) binding. In terms of dynamic binding, "function binding" is the process by which the version of a function to use for a function call is determined. A very good example of dynamic binding is the function call (*Calc)(num1, num2), in the above program, performed through the function pointer (*Calc). In what way is this a good example? Continue reading.

Upon examining the above program closely, it is not hard to see that the function handed to (*Calc) is ultimately dependent upon the user's response to the mathematical calculation prompt. Let us take things a bit further. Because the function handed to (*Calc) is ultimately dependent upon the user's response, the only way that the function address held by (*Calc) can be known is until after the user has responded to the prompt.

User input, as it would happen and as you will agree, does not occur until the program has begun running and is in the process of execution. Moving along: it is common knowledge that a program cannot be executed until it is first compiled (converted into ones and zeros that a computer can understand). Therefore, the compile process comes before program execution. What does all of this mean? Because it is not until program execution that the user provides a response to the prompt, and because the user's response to the prompt determines the function address handed to (*Calc), the computer has no way of knowing the function address held by (*Calc) during the compile process. This is so, take note, because user input - the means by which (*Calc) is handed the correct function address - does not occur during the compile process. User input requires a running program.

With this in mind, when the above program is compiling and encounters a call performed through the function pointer (*Calc), the computer "puts off" dealing with that function call: the computer "delays" associating the call performed through (*Calc) with an actual function body address until after program execution has begun.

User input having occurred, the computer is then ready to relate the call performed through (*Calc) to the address of the correct function body. This feat is performed by means of the switch( ) statement. The correct function body address, you see, is dependent upon the variable taken by the switch( ) statement as an argument (which happens to be the variable that holds the user's response to the mathematical calculation method prompt). The 'case' option chosen within the switch( ) statement, in summary, determines the function body address given to (*Calc). Let us continue. Once program execution has begun, and the user has responded to the mathematical calculation method prompt, (*Calc) is finally handed, within the switch( ) statement, the function body address appropriate to the response, and the function call performed through (*Calc) can be successfully carried out. Dynamic binding will be discussed in greater detail in the next section on polymorphism.


class pointers:

Just as the C and C++ built-in data types (such as char, float, and int) can be declared as pointers, so can structs, and, most importantly, classes. Why, one may ask, would we want to declare a class as a pointer? Before proceeding on this topic of class pointers, it is necessary that we return to the previous section on classes to confront what could be considered to be a possible source of confusion concerning declaring classes as pointers. In the section on classes, we were introduced to the concept of the constructor and destructor. A constructor's purpose is to initialize the data members of a class. It is at the moment a class variable is declared that the constructor of that class is called.

We were then made familiar with the concept of the destructor. The purpose of the destructor of a class, quite clearly, is to "clean up", through the use of delete, after all previous usages of new (performed upon data members of the class) during the "lifetime" of a class variable. It is at the moment a class variable reaches the end of its "lifetime". so to speak, that the destructor of that class is called. A "class variable", as you may recall from the previous section, is also known as an object.

The lifetime of a regular class object "begins" at the moment it is declared (as in Pet pet). This brings us back to the topic of class pointers. Like data members of a class, memory can be allocated for class pointers by means of new. Therefore, just as the lifetime of a regular class object could be said to begin at the moment the object is declared (as in Pet pet), the lifetime of a class pointer "begins", equally so, at the moment the pointer is used to allocate memory for an object by means of new (as in pet = new Pet). Either way, a class variable is "created"; a class variable can take the form of either a regular class object or a class pointer. A class variable, as we've already learned, is the means by which classes are brought into being.

What must be clearly understood here, firstly, is that the constructor (and destructor) of a class fulfill their purposes by acting upon the data members of their class. Things are different, however, with a class pointer. Although the allocation of memory by means of new applies to both class pointers and to the usage of new upon a data member of a class (data members of a class most usually being acted upon by means of new during the lifetime of an object), a class pointer is not a data member. Rather, a "class pointer", like an object, could be considered to be a class variable: a "bringing into being" of a class. Therefore, when memory is allocated for a class pointer by means of new, the program is allocating memory not for a data member (a variable contained within a class), but rather for the class itself.

Let us return to our study on class pointers. Why would one want to declare a class as a pointer? First of all, declaring a class as a pointer allows a program to use that pointer to perform dynamic memory allocation using new and delete, as was discussed in detail within past sections. In review: through the skilled use of dynamic memory allocation using new and delete, a program has the potential to possess unlimited memory throughout the duration of a program's execution: if space is "set aside" for memory using new for only as long as the memory need be used, and if the space is afterward "given back", using delete, when the memory is done being used, the program will never run out of space with which it can perform further dynamic memory allocation.

To understand the second reason for declaring a class as a pointer does not require of us to go much beyond what we learned in C: assume that the address of an object (after having been declared) is passed to a function. (As with struct variables, objects can also be passed to and returned from functions.) Because the address of this object is being sent to a function, all modification to the data members of the version of the object within the called function (the address of the object having been received by the function as a pointer) will also apply to the version of the object whose address was sent (when the function was called).

As the means of expressing the idea of passing the address of an object to a function, consider the following program:

class Obj
{
   public:
      void Func (Obj *);  // task member function.
      int GetVar();      // access member function.

   private:
      int var;           // private data member.
};

void Obj::Func (Obj *obj)
{
   obj->var = 5;
}

int Obj::GetVar()
{
   return var;
}

void main()
{
   Obj obj;         // class Obj object declared.

   obj.Func (&obj);    // address of object passed
                      // to Obj::Func().

   cout << "var is "     // access message.
        << obj.GetVar()  //
        << '.';
}

OUTPUT:

var is 5.

Do not be alarmed if the contents of this program looks unfamiliar; nothing appears in this program that we haven't covered already. At the beginning of the program lies the class definition of class Obj. Within the class definition of class Obj, 2 public member functions are declared: Func( ) and GetVar( ); the private data member var is declared. The presence of var within this class definition is no surprise, given that one of the purposes of a class definition is to declare the data members of a class. Func( ) receives a class Obj pointer as a parameter (which is another way of saying that Func( ) will receive the address of a class Obj object). GetVar( ) is an access member function which enables those external to the class Obj environment to obtain the current value held by the data member var. GetVar( ) fulfills its purpose, quite clearly, by returning the contents of var.

This brings us to main( ). At the beginning of main( ), a class Obj object is declared. Within the second statement of main( ), the class Obj object is used to perform a function call to Func( ) in which the address of the class Obj object is passed to Func( ). Func( ) then receives the address in the form of a class Obj pointer (which is no surprise given that pointer variables hold addresses). As you can see, the class Obj object is used to access the private data member var within the function (through the use of the arrow operator (->) ). Wait a minute, one may ask, haven't we learned from the past section on classes that it is impossible to access a data member from outside of its class (through a class variable), since data members are most usually declared private? This is not a problem: because the object which Func( ) receives as a parameter, and the class to which Func( ) belongs, are both of the same class (class Obj), the received class Obj pointer has direct and complete access to the private class Obj data member var. This also applies to data members declared protected within a class.

In further review: when the class Obj object whose address was received as a parameter by Func( ) is modified inside of Func( ), what is truly being acted upon is the "data member struct" of the object. A "data member struct", as it would happen, is the struct that every declared object possesses; a data member struct contains the data members of an object as members. The bottom line: because the address of a class Obj object was passed to Func( ) (from inside of main( ) ), all modification to the data member struct of the version of the object within the called function (the address of the object having been received by the function as a pointer) will also apply to the version of the object whose address was sent (when the function was called).

The results of the modification to var inside of Func( ) being reflected in the function call performed inside of main( ) can be observed in the output of the third and final statement of main( ), which is a cout statement. As you can see within the cout statement, it is through the class Obj object (obj) that the public member function GetVar( ) is accessed; GetVar( ) returns the current value of the private data member var out from within the class Obj environment and based upon what is returned, the contents of var is outputted. As can be seen in the OUTPUT of the above program, the cout statement outputs 5 as the contents of var, which is an accurate reflection of the value given to var inside of Func( ).

Finally, declaring classes as pointers makes possible yet another powerful feature of object-oriented programming that makes object-oriented programming what it is: polymorphism. Polymorphism is to be discussed in the next section. Take note that just as inheritance is not possible without the use of classes, polymorphism, equally so, is not possible without the use of inheritance.

To examine how class pointers can be used in a way quite similar to how a regular class object works, we will view a program demonstrating the use of class Pet. Short and simple, the purpose of this program is to demonstrate the use of a class pointer to send a message to that class. Furthermore, the program expresses the use of new and delete to create and destroy a class Pet object.

#include "pet.h"

void main()
{
   Pet *pet;    // class Pet pointer declared.

   pet = new Pet;    // memory allocated.

   pet->GreetBoy(); // calls Pet::GreetBoy()
                    // [base class pointer;
                    // base class function]

   delete pet;  // memory deallocated.
}

OUTPUT:

Pet said [Pet greeting!]

At the beginning of the program is the #include statement, giving the program access to the class definition file of class Pet. In the first line of program code, a class Pet pointer (pet) is declared. Next, the class Pet pointer is assigned to a class Pet object by means of new. Upon the program encountering the statement pet = new Pet, the first action to occur is for the program to allocate ("set aside") the memory required to bring the class Pet object into being (using the class Pet pointer). Second and finally, the constructor of the class Pet object is called. In the next statement, a message is sent to class Pet.

As we learned earlier in this section, sending a message to a base class through a regular base class object (by means of the dot ( . ) operator) by specifying a member function of that base class (as in pet.GreetBoy( ) ) calls that particular base class member function. The same holds true, take note, when accessing a base class member function through a base class pointer: the base class member function specified by the message is called. However, using a class pointer to send a message is somewhat different than sending a message through a regular class object: rather than the dot operator ( . ) being used to call GreetBoy( ) (as is the case with a regular class object), the arrow operator (->) is used. Continue reading.

Recall, if you will, from C, the use of the arrow operator upon a struct variable: if a struct variable (such as var) is a pointer, using -> to access a struct member named member (as in var->member) is a shortcut for the more awkward syntax of (*var).member. Let us continue. class pointers, by means of the arrow operator, can access the public member functions of their class in the same way that a regular class object uses the dot operator ( . ) to access the public member functions of that object's class (a possible usage of the dot operator being pet.GreetBoy( ) ). This use of the arrow operator to call a class Pet public member function (through the class pointer pet) can be observed in the above program, in the form of the statement pet->GreetBoy( ). Finally, at the end of the above program, delete is applied to the class Pet pointer for which memory had been "set aside" by means of new.

When the class pointer pet is acted upon by delete, the first action to occur is the calling of the destructor of class Pet. Upon being called, the destructor of class Pet then therefore "cleans up", also using the delete operator, after all previous usages of new that had been performed upon data members of the class Pet pointer (during the "lifetime" of the class pointer). Second and finally: the destructor of class Pet having been called, the class Pet pointer itself is then deallocated altogether.

The usage of new and delete upon a class pointer in this manner is not unlike the behavior of a regular class object: when a regular class object goes "out of scope" (reaches the closing brace - } - of the pair of braces within which the object was declared), the object reaches the ending of its "lifetime". The lifetime of the object having ended, the destructor of that object is called. Second and finally, the object is then, so to speak, "destroyed" completely. Take note here that for a class variable to be "destroyed" and for a class variable to be "deallocated" are two concepts that have equal meaning.

to
previous
section
to
table of
contents
to
next
section


Comments, questions, feedback: jsfsite@yahoo.com