5. Polymorphism in C++
Fifth and final article in series.
Sherman Chin, Wednesday 06 October 2004 - 10:24:44

We have finally reached the third and final major concept of object-oriented programming - polymorphism. The first is classes and the second is inheritance. They were discussed in my previous tutorials so please look through them if you have not done so.

What is the big deal about polymorphism besides being a mouthful to pronounce? Well, polymorphism allows us to call C++ member (class) functions from our objects without us worrying about which class the objects belong to. More specifically, polymorphism means that a call to a member function will cause a different function to be executed depending on the type of object that invokes the function. In English, polymorphism means, "taking many shapes".

How will learning polymorphism benefit us? For one thing, Component Object Models (COM) makes extensive use of polymorphism - I will be discussing COM when I delve into Microsoft DirectX programming in my upcoming articles. Closer to home, we can see polymorphism being implemented in our ongoing RPG game program to generate random Character encounters.

I am going to modify our game code as follows:


#include <iostream.h>
#include <string.h>

class Character
{
private:
char strName[25];
int level;
int hitPoints;
int experience;
public:
void setName(char* ptrName)
{
strcpy(strName, ptrName);
}

virtual void talk(void)
{
cout << "--------------------------------------" << endl;
cout << "Hello! I am a character." << endl;
cout << "My name is " << strName << "." << endl;
}

void walk(void)
{
// We have to write the code to make the character walk here.
}
};

class Wizard : public Character
{
public:
void talk(void)
{
Character::talk();
cout << "I am a wizard as well." << endl;
}

void castSpell(void)
{
cout << "I can cast spells." << endl;
}
};

class Fighter: public Character
{
public:
void talk(void)
{
Character::talk();
cout << "I am a fighter as well." << endl;
}
};

void main(void)
{
Character adam;
adam.setName("Adam");
Wizard merlin;
merlin.setName("Merlin");
Fighter connan;
connan.setName("Connan");

Character* ptrCharacter[3];
ptrCharacter[0] = &adam;
ptrCharacter[1] = &merlin;
ptrCharacter[2] = &connan;

for (int i=0; i<3; i++)
{
ptrCharacter[i]->talk();
}
}

If you compile and run the piece of code, your output will be:

--------------------------------------
Hello! I am a character.
My name is Adam.
--------------------------------------
Hello! I am a character.
My name is Merlin.
I am a wizard as well.
--------------------------------------
Hello! I am a character.
My name is Connan.
I am a fighter as well.

If you are observant enough, you will notice that the output is roughly the same as our previous program in our last tutorial. The only difference is that I have removed the use of the Wizard::castSpell(void) function for the sake of simplicity and clarity. Hence, the only actual difference between this source code and the previous one is that this one uses polymorphism while the previous does not - we still achieve exactly the same output.

Let us further scrutinize the code. The only change I have made to the code in the 3 classes is in the base Character class - I have added the keyword "virtual" in front of the talk member function: virtual void talk(void). Effectively, I have made talk() polymorphic by designating it as a virtual function, that is a base class function which is able to invoke its appropriate derived classes member functions with the same name as it.

The next modification in our source code is in the main function. We have added:

Character* ptrCharacter[3];


ptrCharacter[0] = &adam;
ptrCharacter[1] = &merlin;
ptrCharacter[2] = &connan;

We declare a pointer array of type Character. This array can contain 3 values (element 0 to 2). For the first element, indexed 0, we assign it the address of our adam object. For the second element, indexed 1, we assign it the address of our merlin object. Lastly, we assign the third element, indexed 2, the address of our connan object. If you unfamiliar with any of the syntax used here, please refer to last week's article on pointers in C++ also written by yours truly.

At the end of our main function, we have a "for" loop which goes like this:


for (int i=0; i<3; i++)
{
ptrCharacter[i]->talk();
}

Since I never explained about the "for" loop before. Beginners might benefit from this brief description: A "for" loop is a logical construct that has the syntax:


for (int someIntegerVariable = initial_value; LOGIC_TO_CONTINUE_LOOP; 
LOGIC_TO_INCREMENT_LOOP) 
{ 
// statements to be looped 
};

In our "for" loop, we first declare an integer variable i that is initialize to the value 0. Next, we are stating that we want the loop to continue as long as the value of i is less than 3 (meaning it will stop when the value is 3). Finally, we state that that i should be incremented by the value of 1. Incidentally, i++ is a shortform of i = i + 1; Therefore, the statement in our loop will change as follows: ptrCharacter[0]->talk(); ptrCharacter[1]->talk(); and ptrCharacter[2]->talk(); throughout the loop. Hence, we have actually called adam.talk(); merlin.talk() and connan.talk() because we have assigned the objects' addresses to the values of the ptrCharacter array earlier on.

At runtime, when the function call, talk(), is executed, code that the compiler placed in the program finds out the type of the object whose address is in ptrCharacter and calls the appropriate talk() member function: Character::talk(), Wizard::talk(), and Fighter::talk(), depending on the class of the object. Selecting a function at runtime is called late binding or dynamic binding. Binding means connecting the function call to the function. Connecting to functions in the normal way, during compilation is called early binding or static binding. Static binding is used in non-OOP languages and it cannot support polymorphism.


However, if we want random encounters in our RPG, let us implement code to make a random character speak:

#include <iostream.h>
#include <string.h>
#include <stdlib.h> // for srand() and rand()
#include <time.h> //for time()

class Character
{
private:
char strName[25];
int level;
int hitPoints;
int experience;
public:
void setName(char* ptrName)
{
strcpy(strName, ptrName);
}

virtual void talk(void)
{
cout << "--------------------------------------" << endl;
cout << "Hello! I am a character." << endl;
cout << "My name is " << strName << "." << endl;
}

void walk(void)
{
// We have to write the code to make the character walk here.
}
};

class Wizard : public Character
{
public:
void talk(void)
{
Character::talk();
cout << "I am a wizard as well." << endl;
}

void castSpell(void)
{
cout << "I can cast spells." << endl;
}
};

class Fighter: public Character
{
public:
void talk(void)
{
Character::talk();
cout << "I am a fighter as well." << endl;
}
};

void main(void)
{
Character adam;
adam.setName("Adam");
Wizard merlin;
merlin.setName("Merlin");
Fighter connan;
connan.setName("Connan");

Character* ptrCharacter[3];
ptrCharacter[0] = &adam;
ptrCharacter[1] = &merlin;
ptrCharacter[2] = &connan;


// Seed the random-number generator with current time so that
// the numbers will be different every time we run.
srand( (unsigned)time( NULL ) ); 

// Perform modulus 3 to make sure the random number is from
// 0 to 2
int randomNumber = rand() % 3; 

ptrCharacter[randomNumber]->talk();
}


First of all, I have added:

#include <stdlib.h> 
#include <time.h>

The header files are needed for our srand(), rand() and time() functions that we will be using. Next, I replaced the "for" loop in our main function with: 


// Seed the random-number generator with current time so that
// the numbers will be different every time we run.
srand( (unsigned)time( NULL ) ); 

// Perform modulus 3 to make sure the random number is from
// 0 to 2
int randomNumber = rand() % 3; 

ptrCharacter[randomNumber]->talk();

The srand() function is to seed the random-number generator with a random value. We pass it the time() function as an argument. The time() function returns the current time and thus changes every second. After which, we declare an integer variable randomNumber that is assigned with the random number modulus 3. rand() returns the random number whereas % is the modulus operator. % returns the remainder of a division e.g. 5 % 3 = 2.

For our finale, here is a technical note on how polymorphism works. You may wish to skip this if your are not too technically inclined. However, I advice against this as the following "How It Works" greatly improves your understanding of the Microsoft DirectX documentation - the section on COM. When a member function of a normal object (a one that does not use virtual functions and polymorphism) gets invoked, the compiler has to pass the object's address in memory to the member function. The "this" pointer, which is a special pointer automatically generated by the compiler in every C++ object, contains the address of the object to be passed to the member function. The "this" pointer is the only connection that is needed between an object and its normal member functions.

With virtual functions and polymorphism, the compiler creates an array of member function addresses. This array is known as the virtual table, vtable or vtbl, depending on the compiler. Objects that are derived from base classes that have virtual function i.e. our Wizard and Fighter classes, have another special pointer automatically generated by the compiler, namely the "vptr" or "vpointer" that contains the address of the class virtual table. Therefore, objects that use these virtual functions (polymorphism) are slightly larger than normal objects because they contain the "this" as well as the "vptr" pointers. When a virtual member function is invoked, the compiler creates code to check the object's "vptr" and then uses this to access the appropriate member function address in the class virtual table. Observe that this is opposite of normal objects where the address of the object from the "this" pointer is passed to the invoked member function. Hence, in polymorphism, the object itself determines what function is called at runtime, rather than the compiler during compile time.

I hope that you have enjoyed today's tutorial because I will be leaving the C++ arena for a while to venture into Win32 programming. Actually, we will still be using C++ but we will be implementing some Microsoft Windows graphics instead of the plain command line console. I know that there are still many areas that I have left out of our C++ tutorials but I am hoping to come back to them later or you may get bored staring at the black and white command line all the time - it is about time we put some graphics into our game. For those of you who still feel the enigma of C++, please do e-mail me at Sherman at Sherman3D.com and tell me of future topics that I should cover. And for those of you, who think that I have not done a good job, please feel free to contribute your own articles and tutorials to be put up on Sherman3D. Remember, our mission is to share IT knowledge with the masses. ^_^




this content item is from Sherman3D
( http://sherman3d.com/S3Dplugins/content/content.php?content.6 )