Class organization
This defines the order in which things are defined for a class. There are four main groups:
- All public members except for (non-static) data members.
- All protected members except for (non-static) data members.
- All private members except for (non-static) data members.
- All data members in the following order: public (should not have any of these), protected, and private.
Inside of the first three groups, the order of definitions is as follows:
- virtual methods which override methods of a base class. (One could include comments between blocks of functions which have the name of the base class which first defines the method.) The access should stay the same for derived classes, i.e. once a virtual member is declared protected it should not be later deracated to private.
- Virtual methods which are first defined in this class (We presume that such methods will get overridden by derived classes). Again, explicitly include access here.
- static member functions
- member functions
Other Conventions
Tabs are encouraged, and should be the equivalent of 4 spaces.
Non-Conventions
These are not explicitly spelled out since doing so would cause more trouble than remaning silent.
Commenting style (except that // may ''not'' be used used in C code)
Brace, parenthesis, stuctured style
Line lengths (withing reason, of course...)
The guiding principles are clarity and self-consistency. It should be easy (and not too painful) for othres to mimic the style. The primary developer of a module gets to set the style for that module.
Debugging
Any program or sub-program (module) must have as a minimum two distinct parts: the production version and the debuging version. Typically there is only one prodcution version, while there may be many levels of the debugging version. This is implemented using conditional compilation. The following describes an ideal debugging module. It should be impossible to use it incorrectly. Literally. Any sequence of calls coupled with any value for parameters should either succeed (which should guarantee correct usage), succeed with warnings, or fail. Thus at a mimimum, all input parameters must be checked for acceptable values, as well as all states, which can be checked given a particular function. A new developer (or more likely the original developer many months later), should be able to add functionality to the module without any concern that it may silently violate previous assumptions.Thus all private and protected functions must be exactly as robust as the external interface.
While C/C++ is not the ideal language to accomplish this goal, it is often easier to do that one may suppose. The crucial asset which can be used for this task is the assert. If, while writing some code, every assumption is explicitly stated using as assert, it becomes very difficult to voilate the integrity of the module. Comments about assumptions should be considered obsolete, except as commentary on an assert. The two primary obstacles to the success of achieving the goal of an ideal module are fear of inefficiency and laziness. Both must be fought constantly. If we remember that 90% of execution time is spent in 10% of the code, then it makes very little difference how much extra checking we do in the other 90% of the code. And even in that critical 10%, the only excuse for not checking state is if it actually does run unbearably slowly. In this case one can conditionaly compile more robust checking when tracking difficult bugs. One must always remember that this is not the production version. As for laziness, this is the difficult one. The best trick is for the use of assert's to be so ingrained that it becomes automatic.
Important Note: asserts must never contain side-effects.
External Interface and Internal Framework
The goal for the base framework is to create a platform independent system which is both convenient and powerful. It should also be easy to code robustly using this system (see Debugging above). This framework reperesents a very low-level of inteface. The first (perhaps surprising) decision is that it is written in C. There is one compelling reason for this, which is that all of the target development platforms are currently in C. It is absolutely essential that subparts be testable under a native system. For example, when a hardware/system software bug is discovered, it should be easy to create a test case which can be sent to whom it may concern. They would be helpless if the were given C++ code. (They are helpless enought as it is...) Therefore the layer which intefaces to system must be in C. This framework consists of four main parts:
- Interface to general environment (basically a wrapper/subset of ANSI defined stnadard include files).
- Debugging environment, including assert support, logging facilities, and related convenience routines.
- Memory allocation, especially as related to debugging.
- Low-level framework around platform specific parts.
Implementation
To simplify for the application programmer, only one header file need be included, sys.h. This coupled with some comple time flags determine the debugging state of the program. In some cases libraries compiled with different options mayh be incompatible. (For example, when dynamic memory allocation is being tracked, see below.) This case should generate a link-time error.
For the general environment interface, one unfortunately can not use the standard ANSI include files, because new game platforms usually do not completely implement this specification, or simpluy implement it incorrectly, or both (e.g. 3DO). The following are at least partly supported under this framework (and are implicitly included with sys.h):
assert.h ctype.h limits.h setjmp.h stdarg.h
stddef.h stdio.h stdlib.h string.h
The inteface is the same as specified by the ANSI C book except as noted below. In particular except for the macro assert and the definitions in stdarg.h and limits,h, all names are prefisxed by "sys_" to avoid possible link conflicts with existing implementations. Also, any such name may be implemented as a macro, though in general this will merely be to rename the function. Thus while using side-effects in formal arguments will probably work, it is discouraged. (For that matter, it is discouraged in general).
assert.h : The compile time environment is different. Instead of using NDEBUG to turn of asserts, the macro DO_ASSERTS is defined to be 1 to turn on asserts (default if not defined), 0 otherwise. Defining NO_ASSERT_EXPR indicates that the expression string should not be used in the assertion, significantly reducing the string space of the application.
stddef.h devfines the macro NULL and the typedef size_t