22.12 Runtime class information

When you have an array of polymorphic pointers, how do you tell what kind of class pointer you have? That is, after you set a cCritter* pcritter somewhere in your code, how can you tell if pcritter is just a cCritter* or whether it is perhaps a cCritterBullet* ? (For this discussion, assume that we have a cCritterBullet class that inherits from cCritter.) There are two ways to deal with this.

A hand-made way would be to keep a CString _classname field inside our cCritter class and set it to either cCritter or to cCritterBullet, depending on whether the object was constructed by the cCritter constructor or by the cCritterBullet constructor. And then you could find out if a cCritter * pcritter is really a cCritterBullet* by checking if pcritter->_classname is the same as the string cCritterBullet. People have written programs that way.

In MFC, however, we're encouraged to take advantage of the so-called CRuntimeClass structures. These objects have a CString field for the class name, just like the _classname field of the hand-made approach. They also keep track of how many bytes big one of our class objects might be.

The way that you associate an informational CRuntimeClass structure with one of your class objects? You have to do three things.

  • First, declare your class as a child of the MFC CObject class, or as a child of another class that itself inherits from CObject. The essence of the CObject class is that it has a CRuntimeClass field for storing your class's name in it.

  • Second you have to add a certain macro to the *.h class definition file, as described in the next paragraph.

  • Third, you have to add another macro to the *.cpp class implementation file. There are a couple of different forms of these macro pairs, the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC pair, the DECLARE_DYNACREATE and IMPLEMENT_DYNACREATE pair, and the most powerful pair, the DECLARE_SERIAL and IMPLEMENT_SERIAL. Each takes a couple of arguments like class names and generates a few lines of code. More specifically, the DECLARE line declares a couple of functions, and the IMPLEMENT macro puts code for the methods. See Exercise 22.1 for fuller details.

MFC provides a couple of tools for working with the 'runtime class information' in a CObject -derived class. First of all, there is a macro called RUNTIME_CLASS(classname), which generates a pointer to a CRuntimeClass description of a class if you feed it a class's name just written there without any quotation marks. This macro can only works if the class is CObject derived. Since cCritter inherits form CObject, we can indeed write RUNTIME_CLASS(cCritter) to produce a CRuntimeClass* reference to the kind of class that cCritter is.

The second main MFC tool involving CRuntimeClass information is the BOOL CObject::IsKindOf(CRuntimeClass* pruntimeclass) method. Thus, if you wanted to know if a cCritter *pcritter pointer is actually a cCritterBullet* pointer, you could evaluate pcritter->IsKindOf(RUNTIME_CLASS(cCritterBullet)).

Implementing the runtime class support is actually easier than thinking about it. Here are the three steps mentioned above.

  • Declare cCritter : public CObject.

  • Put this line inside the brackets of, the cCritter class definition.


    Note that it can't just be anywhere in Critter, it has to be inside the cCritter class brackets.

  • Put this line anywhere inside the Critter.cpp file after the #include lines.

    IMPLEMENT_SERIAL( cCritter, CObject, 0 ); 

And we do the same steps for cCritterBullet, except that if cCritterBullet inherits from cCritter, it doesn't have to inherit from CObject. The inheritance relationship is transitive. The IMPLEMENT_SERIAL macro for cCritterBullet will mention the parent cCritter, rather than cObject, as follows.

IMPLEMENT_SERIAL(cCritterBullet, cCritter, 0); 

A word of caution here. You have to be very disciplined about putting in the correct IMPLEMENT_SERIAL macro code into the *.cpp file when you add a new class that has the DECLARE_SERIAL macro in its *.h header. If you don't do it, or if you do it wrong in any way (for instance if you were to put cObject instead of cCritter in the IMPLEMENT_SERIAL macro for cCritterBullet ), then your program will compile and build, but when you try and run it you will get a crash at startup accompanied by an inscrutable message. Try and remember this fact: if you ever do have code that seems to crash at startup 'for no reason,' then you should take a good look at all of your IMPLEMENT_SERIAL macro declarations and make sure that they're all in place and correct.

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