4.6 The code interface


There's two ways of thinking of your body of code: the semantics and the syntax. The semantics of your code has to do with what it means, and the syntax has to do with what the text in your files actually looks like.

At the semantic level, your code is used to prototype and implement classes and to weave some class objects together into your program's run-cycle. That's what we've been talking about so far in this chapter.

In this section we'll say a little about your code syntax, and how you can make the syntax more 'object-oriented' “ in the sense of being more like a black box with an interface of clearly accessible switches and settings.

Of course code is really more of a white box, since you can open up the files and read every bit of them. The point is that if you organize your code in a nice way, you can put something like an interface into it. Here we're not using 'code interface' in the sense of the class prototypes found in the header files “ those are the class interfaces. By code interface we mean a collection of tricks and idioms that experienced programmers use to make their implementation files more tweakable.

C++ language features useful for creating a good code interface include the following.

  • #define switches for #ifdef code blocks.

  • typedef statements for renaming types.

  • static variables .

  • static methods .

Detailed information about #define and #ifdef can be found in the Preprocessor Directives section of the Part II Chapter 22: Topics in C++. Quite briefly , if you have two possible versions of some code, or a piece of code that you only want to turn on sometimes (for instance when debugging), it's a good idea to use a #define and an #ifdef . So you might have something like this example taken from critter.cpp .

 //#define DRAWMOVINGTRIHEDRON      /* This draws red, blue, yellow lines for each critter's          _tangent, _normal, _binormal. It's useful in debugging motion          problems. */ 

Intervening code . . .

 void cCritter::draw(cGraphics *pgraphics, int drawflags)  {      pgraphics->pushMatrix();  pgraphics->multMatrix(attitude());      _psprite->draw(pgraphics, drawflags);  #ifdef DRAWMOVINGTRIHEDRON      cColorStyle dummy;      dummy.setLineColor(cColorStyle::CN_RED);      pgraphics->line(cVector::ZEROVECTOR, 2.0 * cVector::XAXIS, &dummy);      dummy.setLineColor(cColorStyle::CN_BLUE);      pgraphics->line(cVector::ZEROVECTOR, cVector::YAXIS, &dummy);      dummy.setLineColor(cColorStyle::CN_YELLOW);      pgraphics->line(cVector::ZEROVECTOR, cVector::ZAXIS, &dummy);  #endif //DRAWMOVINGTRIHEDRON      pgraphics->popMatrix();  } 

When you have #define -controlled switches like this, be sure to put the #define line up at the top of the *.cpp (or *.h ) file where it's used, and follow it by a long comment explaining the consequences of turning this switch off or on by, respectively, commenting it out or leaving it in.

Another type of code interface switch uses typedef . In the Pop Framework we use a lot of floating point real numbers . So as to avoid having to permanently commit to whether we want to use the faster float or the more accurate double , we have a file realnumber.h that includes these two lines.

 //typedef double Real;  typedef float Real; 

At present we're going for more speed and less accuracy, but if it ever seemed better to have more accuracy at the expense of speed, we would only need to edit those two lines. And everywhere in our files where we need a real number, we always use the defined type Real , rather than float or double . For this to work, of course, we have to have an #include realnumber.h in all of these files.

A simpler kind of code interface setting is a parameter whose value affects the way the program works. Always try and avoid putting raw 'magic numbers' in our code. Instead of a raw number you should either use a #define or, better, a static variable.

Using static variables is an example of a good OO practice that is more common in Java programming. In Java it's very easy to declare and initialize a static variable, you simply place it right into your class definition. In C++ it's a bit harder. You declare the static variable inside your class header, but you have to actually set the variable's value down inside one of your *.cpp files. As we show in Figure 4.14, a class will have multiple object instances, but all of these are thought of as showing the same static variables, and they all agree on the values of these variables.

Figure 4.14. Objects share the static members of the class

graphics/04fig14.gif

Sometimes we make our static variables public; in this case they're the closest thing to a global variable that OOP allows. A static variable that you set once and for all is typed as a const . In Windows programming, a color is coded up as a 32-bit integer with the three right-most byte fields representing the red, green, and blue intensities, which can range from 0 to 255. An RGB macro assembles three intensities into a color-coding integer. To avoid having to remember all this, we can make public static const int variables in, say, a cColorStyle class. The colorstyle.h header would have code like this.

 class cColorStyle: public CObject  {  public:  //Color constants.      static const int CN_RED;  //More code...  }; 

And the colorstyle.cpp would instantiate the static variable, giving it a place to 'live' by using a line like this.

 const int cColorStyle::CN_RED = RGB(255, 0, 0); 

The line does not appear inside any method, it's simply in the file as is. Note that when you instantiate a static you also are allowed to initialize it. Unlike Java, C++ won't let you initialize a static inside the class prototype in the header file. (Well, actually ANSI C++ will let you do this if it's a const static, but Microsoft C++ won't in any case.)

If a static variable is public, we can access it in any file of our code, assuming that file has included the header where the static's owner class is protoyped. A typical use of a static looks like this.

 ppolygon->setFillColor(cColorStyle::CN_RED); 

As a matter of good programming, we always prefix a reference to a static by its owner class name and the scope resolution operator '::'. You don't actually need this prefix if you are using the static within a method of the static's owner class, but it makes the code more uniform and easier to follow. It's also good practice to consistently use a different name style for statics; in the Pop Framework we always capitalize them.

We typically instantiate our statics in the *.cpp that matches the *.h file where they're declared. The statics specific to cGameSpacewar are initialized in gamespacewar.cpp , the statics specific to cGamePicknpop are initialized in gamepickn pop.cpp and so on.

The 'code interface' aspect of statics involves using them for parameters that we may wish to change during successive builds of the program “ unlike a fixed constant like CN_RED . When you write a game, there are a lot of values that you may want to change repeatedly. If these are statics initialized (and well-commented) at the head of the *.cpp file, it's easy to keep adjusting them until you see the performance you like.

A few more facts about static variables. You can, if you like, instantiate a static in any *.cpp file you like. Generally it's easier to find them if they live in the file that goes with the header that declares them. But sometimes you may want to group a bunch of statics together, particularly if their initialization values depend upon each other. In the Pop Framework, for instance, we instantiate the 'mutation flag' statics all inside a file we created called static.cpp. The reason is so that we can make sure that these single-bit flag variables don't have conflicting values.

 const int cCritter::MF_NUDGE = 0x00000001;  const int cCritter::MF_POSITION = 0x00000002;  const int cCritter::MF_VELOCITY = 0x00000004;  const int cSprite::MF_RADIUS = 0x00000008;  const int cSprite::MF_ROTATION = 0x00000010;  //Etc. 

If a static has the const modifier, that means you aren't allowed to change its value anywhere in the code, other than in the line where you initialize it. Other statics can in fact be changed. One use of a non- const static might be a variable to keep track of whether any instance of a given class has been initialized yet. Thus we declare a static BOOL FIRSTTIME variable in graphicsopengl.h , instantiate it in the *.cpp as BOOL cGraphicsOpenGL::FIRSTTIME = TRUE , and have some code that, if FIRSTIME is TRUE , sends some informational output and sets FIRSTTIME to FALSE . Or you might use a static int INSTANCECOUNT to track how many objects of a given type have been created, initializing INSTANCECOUNT to 0 and letting the class constructor and destructor respectively increment and decrement INSTANCECOUNT .

There are a number of reasons why it's better to use static variables rather than #define parameters.

  • Type checking is performed on static variables, but not on #define parameters.

  • Since the initialization of the statics is inside a *.cpp rather than inside a *.h , when you change the value of a static parameter, you don't need to recompile as much of your code.

  • If you always put the class name and scope resolution operator in front of your static names , this adds a level of self-documentation to your code that #define names don't give you.

  • Static variable names are less likely to cause namespace conflicts than #define names. That is, if you # define a name like CRITTERSPEED in two different files, your compiler won't like this. But if you have a cGameRunner::CRITTERSPEED and a cGameWalker::CRITTERSPEED there's no conflict.

  • Using statics is a good habit to get into because it's useful for having OO-correct versions of things very much like global variables, such as the single static cRandomizer cRandomizer:: RANDOMIZER object defined in our randomizer.h and randomizer.cpp files. This way, whenever we need a randomizer anywhere in our program, we just use cRandomizer::RANDOMIZER.

  • Since a static is a variable, you can change it inside your code. Thus, for instance, many of our cGame child classes change the value of the static Real cCritter::MAXRADIUS as a way of altering the default maximum size of all the critters.

  • Java doesn't allow the #define statement, so you might as well start learning to live without it.



Software Engineering and Computer Games
Software Engineering and Computer Games
ISBN: B00406LVDU
EAN: N/A
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net