22.11 Polymorphism

Each Pop Framework game is based on a cBiota object which is a specialized kind of array of cCritter* pointers. The program executes by walking through this array and letting each of the member critters call a method. At each step of the game's animation, for instance, we walk through the cBiota array and let each of the member cCritter pointers make a call to its virtual update method.

Rather than having to check which kind of critter we have in each slot, we're able to just let each critter call its own version of the update method. This is what polymorphism is for. In order to make a function behave polymorphically, we have to do two things. We already mentioned this in Section 22.10: Virtual Methods, but it's worth saying again.

The first step in making a method polymorphic is that the function has to be declared as virtual in the base class. As mentioned before, you don't need to put the word virtual in the child class declaration, though you can if you like.

The second step in making a method behave polymorphically is that it has to be stored in a pointer variable rather than in a base class instance variable. That is, consider the difference between these two declarations.

CArray<cCritter, cCritter &> _embeddedarray; 
CArray<cCritter*, cCritter*> _pointerarray; 

Suppose that, for the purposes of this discussion, the virtual cCritter update method is overridden by the cCritterArmed child class to do something different. Now, even if some of the objects in the _embeddedarray are 'really' cCritterArmed objects, when you placed them into the CArray<cCritter, cCritter&>, you had to upcast them into cCritter objects, so now if you walk through this array and call _embeddedarray[i].update(...), you will always end up just using the cCritter::update method.

But, if some of the cCritter* pointers in the _pointerarray are actually cCritterArmed* pointers, then when you walk through this array and call _pointerarray[i]->update(); you will get either the cCritter::update method or the cCritterArmed::update method, depending on the type of the pointer. Since a cCritterArmed class is a child of the cCritter class, a cCritterArmed* can be thought of as a cCritter*, so we are allowed to put it into the array. But a pointer 'remembers' what kind of class it really points to, and this information gets used when a possibly polymorphic method call is made.

This is why the Pop Framework uses an array of cCritter* pointers. It turns out, though, that for reasons having to do with writing the critter data to a file, it works better to use the less obvious array template CTypedPtrArray<CObArray, cCritter*> in place of the expected CArray<cCritter*, cCritter*>. More on this in Chapter 30: Serialization.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference