Here’s a brief brush‑up on C++, with emphasis on some of the trickier points. If you know C and/or Java but are new to C++ you should read this chapter carefully and refer back to it as the various topics come up. This material is taken from a draft of Rucker’s textbook, Software Engineering and Computer Games, (Pearson Educational 2002)and is Copyright (C) Rudy Rucker 2001.
In object‑oriented programming we use a special kind of data structure called a class. A class is quite similar to an ordinary C struct. Particular instances of some class type are called objects. As well as having members which are data fields, a class also has members that are functions. If SomeClass were the name of a class and SomeFunction(…) happened to be the name of one of the class’s member functions, then it would make sense to have two lines like:
SomeClass K;
K.SomeFunction(…);
The first line says that K is an object of the class type SomeClass. You can also say that “K is a SomeClass object” or you can say “K is an instance of the SomeClass class.” Supposing K has a bunch of data fields, what gets put into these fields when you make the “SomeClass K;” declaration? It turns out that when you define any class you also define a constructor function which serves to initialize new members of the class.
The second line says to let the object K call the class member function SomeFunction. SomeFunction might act on K’s data members, do something to the SomeFunction arguments, possibly return a value, or do any combination of these three actions.
Sometimes a class’s constructor function takes arguments. MFC includes, for instance a CPoint class which has a constructor that can takes two arguments, one for each of the Point’s coordinates. In this case you might have a declaration of the following form, creating a CPoint object with coordinates 3 and 7.
CPoint cpdot(3,7);
It is allowable for a class to have several kinds of constructors. The constructors can be thought of as functions that you can use to create objects of the class type.
We need to mention still another way of initializing an object. Suppose that you want to have, say, a global SomeClass variable, but that you won’t know what numbers to initialize the SomeClass with to until well into your code. One approach that you could use would be to use a global pointer to a SomeClass.
SomeClass *sc_ptr;
When you declare a pointer to a class like this, no initialization happens. No constructor gets called. Depending on the context, a junk value like CDCDCDCD0x or the NULL (zero) value gets put into the pointer, and no effort is made to create a legitimate class object for the pointer to point to.
The way that you make *sc_ptr correspond to a legitimate object is that later, down in the code when you find out what parameters, say x and y, which you want to give to the SomeClass constructor, you put a line like the following.
sc_ptr = new SomeClass(x, y);
In C++, new is a special operator which (i) allocates space for a new object of the specified class, (ii) calls the class’s constructor on the indicated arguments in order to initialize the object, and then (iii) returns a pointer to this newly constructed object.
When you create an object of a given class type the constructor gets called; the constructor encapsulates the initialization and the allocation code. You can “create” an object either by declaring it as a variable or by using the new operator to create an object and return a pointer to it.
Note that in Java, all class instance variables are of the pointer type. Java doesn’t explicitly use the * symbol to indicate pointers, but the variables for objects are indeed pointer variables. This is why in Java you need to call new whenever you want to initialize an object variable.
Something that makes the situation a little confusing is that Java also has primitive type variables such as integers that are not pointers.
Saying the same thing again in a different way: in the Java language every class instance is a pointer. Sometimes beginning Java programmers have the impression that “Java has no pointers.” But exactly the opposite is true. Everything in Java is a pointer, other than primitives like int and char. The Java compiler will remind you of this if you try and compile code with a line like SomeClass nogood. You’ll get an error message saying something about a NULL pointer. Java requires you to rewrite the offending line as SomeClass goodnow = new SomeClass().)
Suppose that SomeClass is a class whose prototype includes a member _val and two functions SomeFunction and SomeOtherFunction.
class SomeClass
{
int _val;
int ValSquared(){return _val * _val;}
int SomeFunction(int input);
int SomeOtherFunction(int first, int second);
};
Now if K is an object of type SomeClass, then the value of , let us say, K.SomeFunction(17)will depend on (1) the argument 17, (2) the function definition of SomeFunction, and (3) the contents of the object K. K is an “implicit argument” to this function call.
When you write out the code for SomeFunction, you play on the fact that you have an implicit argument to the function, even though you don’t explicitly show it. Thus the code for SomeFunction might look like the following.
int SomeClass::SomeFunction(int input)
{
return SomeOtherFunction(_val, ValSquared());
}
This means that K.SomeFunction(17)
would be evaluated as K.SomeOtherFunction(K._val,
K.ValSquared()), with K.ValSquared()being
evaluated as K._val * K._val.
The initially bewildering C++ this is used inside a function as a way to refer to a pointer to the implicit calling object. The object itself is *this, that is, the object that this is pointing to. Remember that if p is a pointer, then *p “dereferences” the pointer to stand for the actual object the pointer points to.
If we wanted to, we could have put this‑>_val or *this._val in place of _val inside the SomeClass::SomeFunction definition. But we don’t want to, as this would be defeating the whole beauty of C++’s concise syntax.
In general, when you are looking at some unfamiliar code and you see a function call inside a class method definition, you can usually expect that any unfamiliar function you see being called “naked” is in fact a member of the class. When we speak of a function being called “naked”, we mean that it’s being called without any explicit calling object in front of it, no caller. or pcaller‑> in other words. If you don’t find the function listed as a method of the class, it may be that it’s a method of the class’s parent class.
Now and then we want to explicitly avoid using a class’s own version of a member function, preferring to use a global function with the same name. In this case, we put the symbol :: in front of the function name. The name of this symbol, by the way, is the “scope resolution operator.” We sometimes use it in MFC programming, as most of our MFC class methods have the same names as some global method. In order to make the code easier to understand, when we use any global function at all we will habitually put the :: in front of its name just to remind ourselves that it isn’t a member method of any class.
Just to show that making a class doesn’t need to be hard, let’s do the absolute simplest implementation of, say, a cDisk that represents a circle. Suppose we assume that we specify the circle by a cVector center, a Real radius, and a COLORREF fillcolor.
For a class which has simple fields as members, C++ defines an appropriate default no‑argument constructor, a default copy constructor, a default overloaded operator=, and a default destructor. The default constructor fills all the data fields with zeroes, the default copy constructor and operator= copy the data from one object to another field by field, and the default destructor simply frees up the space used by a class object.
For this very simple example, let’s make all the class data members public so we don’t have to think about mutators and accessors. So now we can define cDisk like the following.
This is a time to remind you that when you write classes of your own, you have to put a final semicolon at the end of the class declaration. This is a possible slip‑up that can lead to really confusing error messages from the compiler.
This is also a time to mention that in C++ we have the convention of starting all of our member variable names with an underscore. This makes your code more readable as then you can easily recognize when something is a member variable. Unfortunately this sound and useful convention was not carried over to Java. Do note that the code will compile just as well if you leave out all the underscores on the private field names; they are there only for the human programmer, the compiler doesn’t care what you call the variables.
class cDisk
{
public:
cVector _center;
Real _radius;
COLORREF _fillcolor;
};
Is that painless, or what? Who says it’s hard to use classes?
Where should we put the class definition? The clean thing to do is to it into its own disk.h file that lives in the same directory as our Pop code. Since we don’t have any cDisk methods to implement, we don’t need a disk.cpp file.
How to make the disk.h file? There are several choices.
If your new class header file is similar to an existing class header file, simply make a fresh copy of the old class header file and change its name.
Or use File | New to create the disk.h file in Visual Studio (or in any other text editor, provided you save it as a Text Only file) and then use the Project | Add | Files... dialog to add it to the Pop project.
Or create and add the file in one step with the Project | Add to Project | New | Files tab | C/C++ Header Files dialog.
Or (least recommended technique) Use the Visual Studio menu selection Insert | New Class...
The author doesn’t recommend the last technique because it throws you into a dialog box situation with a lot of choices whose consequences aren’t immediately clear; and when you’re done, your files have some ugly machine-written code that you truly don’t need.
One caveat; when you make your own header file, don’t forget to bracket it with the following lines to prevent double header-file includes (as discussed in Chapter 20: Using Microsoft Visual Studio).
#define DISK_H
#ifndef DISK_H
... //The class prototype code goes here
#endif //DISK_H
However you end up making your new header file, you need to tell the files that want to use it about the class, but putting an #include "disk.h" into them.
When you have classes with methods you need to implement, you need to make a file like disk.cpp to put the implementation into. You can make this new file in any of the ways mentioned above. Be sure that the first two lines of this file are these
#include "stdafx.h"
#include "disk.h"
As well as a constructor, each class has a destructor method with a name like ~SomeClass. (Recall that the "~" symbol is on the upper left hand corner of your keyboard.) We have no choice about the names of the constructor and destructor methods. For any class SomeClass, the constructor is called SomeClass and the destructor is called ~SomeClass.
When a variable goes out of scope its destructor is automatically called. Thus if your program has a global variable, the variable’s destructor is called when your program terminates. If you have a function which has a local variable sctemp in it of type SomeClass, the SomeClass constructor is called when the code execution hits the line where the SomeClass variable is declared, and the SomeClass destructor code for sctemp called when the execution hits the closing bracket of the function. More precisely, when you hit the closing bracket of a function, the destructors are called for all of the local variables that were declared inside the function. The destructors for local variables are called in the reverse order that the constructors were called.
In the case where you use the new operator to create a pointer to a SomeClass instance, you must explicitly call the delete operator on the pointer to call the destructor and free up the memory.
When you look at documentation on classes, you see a lot of pesky const . What's all that about? Well, first off let’s say that if they confuse you too much, it would in fact be OK to just leave them all out. But there is a reason for them.
It's generally considered a good idea to put const after the declaration of any function with does not change the values of a class's private fields.
int Func(SomeClass input)const; //Doesn’t change the caller class
int Func(SomeClass input); //May change the caller class
Meticulous programmers (and that's what we should all want to be!) use const as a way of telling the compiler to warn you if it finds anything in that function's code which changes a private class member. If you don't bother to put the const into your class and a meticulous programmer uses your class in another class definition where he or she has const, then there will in fact be trouble, as the compiler will be scared to use your non‑const function in a const function definition.
There are some savage gotchas connected with the use of the const after a function prototype. These have to do with the fact that a C++ compiler internally “name‑mangles” function names so that a function has a completely different name according to whether it’s a const or not. We could put the two Func prototypes above into a class and C++ would compile as if these were completely different functions.
This has two unpleasant consequences.
First of all, the implementation of a function must have the word const or not, matching the prototype. Otherwise you get a compiler error. That is, if either or both of the two different prototypes above were in a cMyClass, they’d be respectively implemented like this.
int cMyClass:Func(cSomeClass input)const{ .... }
int cMyClass:Func(cSomeClass input){ .... }
The second unpleasant consequence is much worse. Suppose you declare a virtual function in a base class and then redeclare it in a child class. (If you’re hazy about virtual functions are, look down at the description later in this chapter.) If the const declarations of the two functions don’t match, then the virtual base class function won’t call the child class function. This happened to the author recently with some code like this.
class cSprite
{
virtual void draw()const;
};
class cPolygon : public cSprite
{
void draw();
};
When one sets a cSprite* psprite = new cPolygon() and calls psprite‑>draw(), one keeps getting the cSprite::draw instead of the cPolygon::draw. This is because C++ viewed our draw() in cPolygon as a new function completely different from the draw()const in cSprite.
Starting to use const is an all‑or‑nothing decision. That is, if you start using const in one file, then the compiler will make you use it in every related file. The reason for this is that if you try, say, to have a class Careful has a const accessor method which uses the non‑const accessor member of a Casual class member, then the compiler will balk. Here’s an example:
class Casual
{
int _val;
public
int val(){return _val;}
};
class Careful
{
private:
Casual _casual_member;
public:
int val() const {return _casual_member.val();}
/* Won’t compile, will give error like "non‑const function
called on const object." */
};
Putting const declarations into your code is a bit of a hassle, but if you plan to have others use your code, you have to do it. Why? Because even if you like to be casual, your user may very well be careful, and, as just explained, when a careful class tries to use a casual class method there can be a conflict.
The & symbol is a way of telling the compiler to pass an object by reference, that is, the compiler generates a pointer to the object and passes that, instead of copying out all the fields of the object. If you have a function with a prototype like the following, you can give it a cSomeClass argument input and Func can in fact change what’s in the input.
int Func(cSomeClass &input);
One reason we do this is because we might actually want to change the fields of the object being passed. Recall that in C, you can't directly change the value of a variable being passed in to a function. But the C++ trick of putting an & into the function prototype lets you have a function which does change the values of its input.
Another reason is that we sometimes want to pass an object as a pointer instead of as a structure is that it’s faster to pass a pointer to an object instead of copying the whole object. And there will be times you want this speed, but you definitely don’t want to change what’s in the input.
The troublesome const also arises in connection with the & symbol in function declarations. Here it relates to the argument of the function rather than to the caller. If you want the speed of passing an argument by reference, but you know you don't want to actually change what's in the object, then you use the const followed by the class name and the & to mean "don't allow any change in this fields of this object, but do pass it as a pointer which you generate." And you use a prototype like:
Func(const cSomeClass &input);
You can combine the const argument and the const function declarations in all the possible ways. C++ views all of these as different functions.
int Func(SomeClass &input)const;
int Func(SomeClass &input);
int Func(const SomeClass &input)const;
int Func(const SomeClass &input);
For full disclosure, we might as well mention four more possible ways that you might prototype a function in C++.
int Func(SomeClass input)const;
int Func(SomeClass input);
int Func(SomeClass *input)const;
int Func(SomeClass *input);
What a hassle, huh? A real nightmare! No wonder so many people want to abandon C++ for the calm of Java. Bjarne Stroustrup, the inventor of C++ claims that the only reason that Java is simple is because it’s a young language lacking all of C++’s features. He says that C++ is so complicated because it’s mature.
But once you get some practice with using C++, you’ll find that having eight possible ways to prototype a function isn’t so bad as you might expect. The practice is always to make every function as const as you can, so you don’t actually have to think about that so much. The different forms let you make sure your code is going to be as portable, safe, and fast as possible.
Remember to match the *.cpp implementation format to the *.h declaration format. And, above all, be careful that your derived classes use the same declarations as the parent classes.
We often have members of classes which are themselves classes. Thus we might have something like this.
class MyClass
{
protected:
ClassA aobject;
ClassB *pbobject;
};
In this kind of situation, we say that aobject is an embedded or instance ClassA member, while pbobject is a pointer-based or reference ClassB member. We often prefer to use pointer‑based members because then the methods of these members can be called polymorphically. When we talk about Serialization in a later chapter, we’ll see that there are some important issues relating to the difference between embedded and pointer‑based members.
Generally you let primitive variables be instance members, and you let your object members be referenced. This is in fact the Java style of doing things. You should generally have in the back of your mind that you might need to port your C++ to Java one of these days, or vice-versa, so, all things being equal, the more you can make your C++ code like Java and your Java code like C++ code the better.
A big win with having a pointer-based reference members is that then polymorphism will work for these members — see our discussion of polymorphism below.
A parent class is a subset of a child class; that is, the child class includes all of the data members and methods as the parent. It’s common also to speak of a parent class as a base class, and to speak of a child class as a derived class.
In order to allow the child class to have access to the private fields and methods of the parent class we need to declare those fields to be protected rather than private.
What about access to the public fields of the parent? The child can always access these, but these fields do not necessarily have to be public members of the child as well. A child can change a parent method from protected to public or from public to protected by redeclaring it.
In the declaration of a child class, you follow the child
class’s name by a colon an access specifier, and the name of the parent class,
like class cMyChild
: public cMyParent.
We usually stick the word public in there because otherwise the access permissions will default to the default C++ value of private, which is not as commonly used. In particular, if you don’t put in the public, then all of the parents public methods are now private methods of the child.
Why would you ever want to use private inheritance anyway? This is appropriate when you have defined a class that is a specialization of a parent class that has some methods you don’t want your code‑users to be able to invoke.
Here’s a specific example. Later we’ll use a class called a cBiota which hold a bunch of cCritter* pointers to lively little cCritter objects. Now cBiota actually inherits from a special kind of MFC class called CObArray which encapsulates the notion of an array of pointers to objects. The CObArray has standard array methods such as GetSize(), operator[], and a method Add() for adding things to the end of the array. Now suppose that when you Add an element to the cBiota, you want to be sure to do some kind of branding on the element, like, say, giving it an internal pointer to the cBiota itself. So if we had a cBiota _biome and a cCritter* pcritter, we might write a line like _biome.Add(pcritter), but we might not want to be able to write a line like _biome[_biome.GetSize()‑1] = pcritter; Now if in this case you want to prevent yourself and the other programmers you work with from using all of the possible CObArray methods, you can declare class cBiota : private CObArray. And then down inside the cBiota definition, you can specify the Add method as public, and overload it to do the critter‑branding.
The author recently stumbled across an odd gotcha related to parent and child classes. C++ will let you declare child‑class member with the same name as a parent class member. If you do this, your child class can be changing the value of this field, but when you use a parent class accessor to look at what you think is the same field, you’ll get back the default value that lives in the parent class. In this situation, the child field is said to “shadow” (as in “cover up”) the parent field. Here’s an illustration.
class cSprite
{
Real _radius; //Assume the constructor sets this to 0.0
Real radius(){return _radius;}
};
class cPolygon : public cSprite
{
Real _radius;
Real makeRegularPolygon(int vertexcount, Real radius)
};
cPolygon poly;
poly.makeRegularPolygon(5, 2.0);//Changes cPolygon _radius to 2.0
Real polyradius = poly.radius(); //Makes polyradius 0.0, not 2.0!
When a class object is constructed , the following sequence takes place:
(i) memory storage for the object is allocated (e.g. enough bytes for the object's data fields are allocated in memory),
(ii) the default constructor of the parent class (if any) is called, unless you explicitly request some other parent constructor in your initializer list,
(iii) the constructors for each of the class member objects are executed (in the order of the member classes' declaration), if there is not initializer list the default constructors are called, but you can request special constructors in your initializer list.
(iv) the class constructor code is executed.
When a class object is destroyed the following sequence takes place:
(i) the class destructor code is called,
(ii) the destructor of each of the class member objects is executed.
(iii) the destructor of the base class (if any) is executed,
(iv) the memory storage for the object is recycled.
So the cMyChild constructor automatically calls the default cMyParent constructor before getting inside its own code. A handy way to think of this is to imagine that a cMyChild object has a cMyParent object as a member. The parent class and the members all get their constructors called.
Now it may be that you want to feed some arguments into the base class or member constructors. To do this, you write out these constructors in an “initializer list” that you place after a colon after the constructor. Here’s an example of a class definition.
cTeacherProgrammer : public cProgrammer
{
private:
int _ugliness;
cGollywog *_pimaginaryfriend;
public:
cTeacherProgrammer(int flubba, float gleep);
~cTeacherProgrammer();
}
And here’s a constructor using an initializer list.
cTeacherProgrammer::cTeacherProgrammer(int flubba, float gleep):
cProgrammer(flubba, gleep),
_ugliness(1000000)
{
_pimaginaryfriend = new cGollywog(this);
}
And here’s how the destructor would be defined.
cTeacherProgrammer::~cTeacherProgrammer(){delete _pimaginaryfriend);}
Note that the cTeacherProgrammer destructor first does the delete _pimaginaryfriend, and then calls the parent cProgrammer destructor.
You can remember the sequence by thinking in terms of working your way down the hierarchy at construction, and working your way back up at destruction.
When you need to code up several different forms of a
constructor it can be useful is to have an initialization helper function that
the different constructors in both the parent and the child class can call.
The keyword virtual in front of a parent class method tells the C++ compiler that the class has a child class which has a method which has the same name but which acts differently in the child class. If (1) a method is declared as virtual and (2) the object which calls the method is referred to via a pointer, then (3) the compiled program will, even while it is running, be able to decide which implementation of the virtual method to use. It is worth stressing that this “runtime binding” only works if you the programmer fulfill both conditions:(1) you use virtual in your method declaration and (2) you use a pointer to your object.
Except in the case of a destructor, corresponding virtual functions have the same name. You don’t put the word virtual in front of the actual function implementation code in the *.cpp. You can put virtual in front of the child class function declaration in the child class’s header file or not, as you like.. In other words, to start with, you really only need to put virtual in one place: in front of the parent class’s declaration of the function.
But in order to make our code more readable, when we derive off child classes from a parent class with a virtual function, we usually do put virtual in front of the child method declaration as well as in the parent method declaration. The child does need to have a declaration for the method in order to overload it in any case.
One slightly weird thing is that the a parent class like destructor ~cProgrammer() needs to be declared virtual even though a child class destructor like ~cTeacherProgrammer() destructor seems to have a different name. But you don’t in fact call the destructor by name. In a program where we have a cProgrammer *_ptextbookauthor, we might be calling either the cProgrammer or the cTeacher destructor with a line like delete _ptextbookauthor; The thing is, it’s possible that _ptextbookauthor got initialized as new cTeacher, so we just don’t know.
The delete operator calls the destructor without referring to the destructor method by name. So the compiled code needs to actually look at the type of the _textbookauthor pointer to find out whether its really a cProgrammer* or a cTeacherProgrammer* so it knows which destructor to use. And unless you fulfilled the “virtual condition” by making the destructor virtual, then the code won’t know to do runtime binding and choose between using the parent or the child method as appropriate. The destructors are different here because the cTeacherProgrammer has more stuff to destroy, in particular, the cGollywog *_pimaginaryfriend. Slogan for a class: If your child is richer than you, you need a virtual destructor.
One final point should be mentioned here. Ordinarily, when you have a method virtual void somemethod() in a base class called, say, cParentClass, then when you overload the method in a child class called, say, cChildClass, the child class somemethod() will call the parent class method if we explicitly ask it to with code like this.
void cChildClass::somemethod()
{
cParentClass::somemethod();
//Your extra childclass code goes here....
}
But in the case of a virtual destructor, the parent class’s destructor method will be automatically called when the object is deleted. This is in accord with the standard C++ execution order of constructors and destructors mentioned in the last subsection.
cChildClass::~cChildClass()
{
//Your extra child class destructor code goes here...
...
//The cParentClass::~cParentClass destructor will be automatically called
//here at the end of the ~cChildClass destructor call.
}
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 the section just above about Virtual Methods, but it’s worth saying this 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 called by a pointer. That is, consider the difference between a
CArray<cCritter, cCritter &> _embeddedarray;
CArray<cCritter*, cCritter*> _pointerarray;
Suppose that, for the purposes of this discussion, the virtual cCritter update method is overloaded by the cCritterArmed child class to do something different. Now, even if some of the objects in the _embeddedarray are “really” cCritterArmed objects, 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]‑>draw(), you will get either the cCritter::draw method or the cCritterArmed::draw 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 bubble 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.
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‑>_name == “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 _name 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 the Exercise below 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
Bubble.h, it has to be inside the cCritter class brackets.
DECLARE_SERIAL(cCritter);
· Put
this line anywhere inside the Bubble.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.
· 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 start-up “for no reason,” then you should take a good look at all of your IMPLMENT_SERIAL macro declarations and make sure that they’re all in place and correct.
In C++, the “::” is called the “scope resolution operator”. If you put a class name like MyClass in front of the operator, this indicates that you want to use the version of the class method implemented by the MyClass code. If, for instance, you have a ChildClass and a ParentClass, then ParentClass::Method() and ChildClass::Method() could very well mean different things.
Some functions are “global” functions that aren’t a member of any class at all. We try to make a practice of always putting two colons with no class name in front of a global function to remind ourselves that this is a function that doesn’t belong to any class. Ordinarily, putting the “::” in front of a global function doesn’t add anything or change anything; the code compiles and runs equally well with or without it. It’s just something you do make your code easier to read. Thus, when I want to refer to the global Windows API method PlaySound, I’ll normally write ::PlaySound.
The one time the “::” would really be necessary in front of a global function would be if you wrote a class method with the same name as a global function. If, for instance, you gave CPopView its own PlaySound method, then if you wanted to call the global PlaySound inside a CPopView method, you really would need to say ::PlaySound, as a call to PlaySound would call the CPopView::PlaySound method.
When a compiler processes C++ code it changes the names of functions to include information about the types of the function’s arguments. This process, normally invisible, is called name‑mangling.
You most often notice the effects of name-mangling when you put a prototype of a class method in a header file, but then fail to implement the method in any of your project’s *.cpp files. The project will compile all right, but at the very end, you will get a linker message. Thus, if you were to leave out the code for the cCritter method int cCritter::move(Real dt), you would get a linker error like the following.
popview.obj : error LNK2001: unresolved external symbol "public: int __thiscall cCritter::move(float)" (?move@cCritter@@QAEHM@Z)
The string at the end at the end of the line is the name-mangled name of the method.
Remember how we talked about how having a const after the name of a function changes how C++ thinks of it? That’s because the const goes into the name-mangled name.
Name‑mangling can become an issue in advanced programming situations, for instance if (a) you want to link someone else’s *.c module with your *.cpp modules, or (b) you want to locate a function by name inside a dynamic link library module. This is because although you may think the function’s name is what you called it, say “int MyFunction(),” C++ will actually have assigned a different “mangled” name for your function. You can turn off the name‑mangling for a function name by specifically asking in its prototype that it be treated like a C function, using a line like the following.
extern "C" int MyFunction();
Compiling a file is actually a multi‑step process. Before actually compiling any code, a compiler uses something called preprocessor to look through the project files and carries out the instructions embodied in the various preprocessor directives. Any line of C++ that starts with # is a preprocessor directive. Some of them are #include, #define, #ifdef, #endif and #pragma. Each preprocessor directive tells the compiler to do something before running. The instructions in the # directives are used to alter the contents of the file, which is only then passed on to the normal compilation process.
What the #include directive tells the compiler to do is to replace a line like #include "whatever.h" line with a full, exact copy of the Whatever.h file, just as if you had used your text editor to block copy the whole Whatever.h file and paste it in.
If the filename after #include is in quotes, the compiler will look for the file in the directory where the project file lives; and if the include file is in pointy brackets, the compiler will look for the file in whatever directories the project file has set to be places to look for include files — typically the standard location for include files is the INCLUDE subdirectory of the directory where the compiler lives, but sometimes you will want to add other directories to the standard search path.
Note that the compiler is not sensitive to the case of the letters used in the names of the include files.
The #define directive has this appearance.
#define ANYSTRING Any other
string
The string between the first two blank spaces after the #define is replaced everywhere by the string which fills out the rest of the line after the second blank space. Who does the replacing? The preprocessor. It effectively does a search and replace, replacing each “ANYSTRING” by “Any other string”. It is good programming practice, but not formally necessary, to always use all capital letters for a quantity which you #define.
Another, non‑obvious effect of a #define statement is that it adds the first string to the compiler’s “symbol table” for the code being compiled; the symbol table being a private list of what strings have been used in a #define.
The next group of preprocessor directives have to do with control flow. Lines of the form #ifdef, #ifndef, #else, and #endif conditionally include or exclude parts of the file depending on whether some symbol has been placed into the compiler's symbol table by a #define.
#ifdef, #ifndef, #else and #endif are preprocessor directives to the compiler. The first two stand, respectively, for “if defined” and “if not defined.” If the expression (or “token”) after the #ifdef has been defined with a #define, then the block of code up until the #endif is included, if the token is not defined, then the code is not included. If the token after an #ifndef has been defined with a #define, then the block of code up until the #endif is not included, if the token is not defined, then the code is included.
As mentioned above, you can #define something without putting a “replacement string” for it. That is, we can have this line:
#define ANYHEADER_H
This use of the #define directive adds the indicated string of letters to the compiler's symbol table so as to possibly affect #ifdef or #endif statements.
There is also a defined() operator one can use in conjunction
with a plain #if
directive. So instead of #ifdef WIN_H, for instance, you can write #if defined(WIN_H), and instead of #ifndef WIN_H, you can write #if !defined(WIN_H).
The #pragma directive is used for miscellaneous kinds of special hints to the compiler. A common use is to turn off a warning that you don’t care about. Thus the following line taken from our realnumber.h file turns off two warnings that result from treating a double as a float.
#pragma warning(disable: 4305 4244)
Sometimes you don’t want to bother using a pragma to turn off a warning message of the form, say, “information lost in conversion from double to int”. You can tell the compiler that in a particular case you really don’t mind rounding, say, sqrt(dx*dx + dy*dy) off to the closest integer. In C we would have done this with a “cast” by putting (int) in front of the number. In C++ it’s more common to write a cast as a “constructor” by putting int(sqrt(dx*dx + dy*dy)). In general, if you can get rid of a warning message by adding a little bit of code it’s worth doing, so that then you know that any messages that do pop up in your compile are important.
Although typedef isn’t a preprocessor directive, it has much the same force. typedef gives us a mechanism for defining a synonym for an existing type. The syntax is as follows:
typedef existing-type synonym;
If you want a synonym for a class name then its better to use typedef than define, as the compiler can then better check the consistency of what you’re doing.
We might use a typedef in the case where a type name is very long and unwieldy. Thus we might do something like this (although actually we don’t use this in the Pop Framework.)
typedef CTypedPtrArray<CObArray, cCritter*> cCritterArray;
Another case where we use typedef is when we want to try out different builds of our code. The Pop Framework has two possible typedef for the Real type in realnumber.h, and in vectortransformation.h, it has a typedef based on whether we plan to use 2D or 3D graphics.
#define THREEDVECTORS
#ifndef THREEDVECTORS
typedef class cVector2 cVector;
typedef class cMatrix2 cMatrix;
#else //THREEDVECTORS
typedef class cVector3 cVector;
typedef class cMatrix3 cMatrix;
#endif //THREEDVECTORS
Suppose you want to have an array that holds, say, cCritter objects. There are really three options: maintain your own C‑style array, use an array template from the ANSI C++ STL Standard Template Library, or use the Microsoft CArray template.
To maintain an array yourself means using a declaration like cCritter *_critter or cCritter _critter[]. (Note that C regards these two declarations as equivalent; in C an array is simply a pointer.) If you do this, you would also need an int _critter_count variable to keep track of the current number of points stored. And you'd either have to preallocate _critter to some generously large size (and worry about eventually writing off the end), or you'd have to keep reallocating the memory for the array as it grows.
That's a lot of work, and it's easy to make errors in doing it. Writing your own array code these days means reinventing the wheel. It's much better instead to use a presupplied array template. Here we have two choices: STL or MFC.
STL “vector” arrays. Every version of C++ comes with a platform
independent library of templates known as the STL or the Standard Template
Library. This library was written at
Hewlett‑Packard around 1995 and is now part of the official ANSI C++
language standard, which means that all C++ compilers for every platform must
support it. The STL library includes,
among many other kinds of collection classes, an array template that's known as
vector. The code for the STL template will normally
be found in a file in your Visual Studio INCLUDE directory; the name of this
include file is, a bit oddly, just plain VECTOR, with no file extension. For whatever reason, Visual Studio needs a
bit of a nudge to let the STL work; whenever you include an STL file you need
to follow it with a code line of the form using
namespace std; To create
an STL array of some class type T,
you use the new type vector<T>.
One plus of STL templates is that they’re portable to all C++ platforms, so you can expect to be able to use them in more environments. But there are some annoying gotchas associated with the now fairly old STL templates — I won’t go into them here. Certainly the Visual Studio documentation of the STL is not as easy to use as is their documentation of the CArray. And the CArray templates are tweaked so as to work smoothly with the MFC method of “serializing” or saving and loading files. It’s simpler to give in and do it the Microsoft way instead of trying to use STL. We’re going to use the MFC CArray template in this book.
MFC “CArray” arrays. The code for the CArray template is in a file called afxtemp1.h. If you want to declare some CArray variables in a file, you need to add this line to the top of the file, so the compiler will know what we’re talking about.
#include <afxtempl.h>
To create an array of objects of some type T, we normally declare our new type as CArray<T, T&>.
The meaning of the second mention of T in the declaration is that this is the type used in some of CArray’s internal functions that do things like add your elements to the CArray. The idea is that one normally uses the pass by reference for these types so as to marginally improve the speed of your code, by effectively passing pointers instead of copying whole structures. In the case where T is a primitive type like an int, we don’t bother with the & for the second argument.
Template Usage for |
STL |
MFC |
Include instructions needed in file. |
#include
<vector> using
namespace std; This include should appear only in the files where <vector> is used, and it should be the last include listed at the head of these files. |
#include
<afxtempl.h> |
Prototype of array of classes. |
vector<cCritter>
_critter; |
CArray<cCritter,
cCritter &> _critter; |
Prototype of array of basic types. |
vector<int>
_radius; |
CArray<int,
int> _radius; |
Adding element to end of array. |
push_back(newpoint) |
Add(newpoint) |
Deleting the array member in the i slot. |
erase(i) |
RemoveAt(i) |
Accessing size of the array. |
size() |
GetSize() |
Setting the array size. |
resize(newsize) |
SetSize(newsize) |
Accessing an element for reading or setting. |
[i] |
[i] ElementAt(i) GetAt(i),
for read only |
The default size of _critter in either case will be 0, and if you immediately try and execute a line that sets, say, _critter[1] to something, then your program will crash. Adding an element to an array will automatically grow it to a size large enough to hold the new element (i.e. it grows the size by one). But if you are planning to read a lot of elements into your array using the [] operator, you need to set the array’s size to be large enough before you start.
Another thing about array templates that’s important to be aware of is that when you add an element of type T to your array, it’s a copy of your element which actually goes into the array rather than the element itself. So you need to be sure to have a good copy constructor and an overloaded =operator defined for any class of objects that you’re going to put into an array template.
Actually we’re going to be using a variation on the CArray template, and this is the CTypedPtrArray<CObArray, cMyClass *> template. This is used for holding an array of pointers to instances of some class we’ve written called cMyClass. For this to work, the cMyClass has to inherit from the MFC CObject class, which is a kind of universal base class in MFC. More on this below.