The Destructor
The code for a normal destructor is straightforward. If the data pointer is valid, the motor is forced to a stop and the actual disposal code is just two lines; see Example 3.
Example 3: Destructor.
void MotorXtor(MotorData* d) { if (d) { *d->mPort = 0; // ensure motor stopped Free(d); DestroyVtable(Motor, &InstCount); } }
The class data is destroyed when the destructor calls the Free
function. This is defined in the C_Classes header, and like the Allocate
function, is a wrapper around the C library malloc/free
heap memory management. Free
decrements the internal counter that was incremented by Allocate
.
The vtable
class's destruction is handled by the DestroyVtable
function that is also defined in the C_Classes header. While the constructor increments the class InstCount
variable, it is decremented in DestroyVtable
. The counter going to zero implies that this is the last instance of the class to be destroyed, and consequently, the vtable
is deleted from RAM by a call to Free
.
All functions in a class code file are declared as staticthe only access is via the class pointer. Listings One and Two show the C_Classes header and code files. The Motor
class header (plain.h) and code files (plain.c) are available for download here.
Listing One
/****************************************************************************** PROJECT C Classes NAME C_Classes.h PURPOSE A header file for C classes derived using the C_Class methods. AUTHOR Ron Kreymborg, Jennaron Research ******************************************************************************/ #ifndef _C_CLASSES_H #define _C_CLASSES_H #include <stdlib.h> #include <memory.h> #define TRUE 1 #define FALSE 0 //----------------------------------------------------------------------------- // A macro to copy the vtable structure in rom into ram. The VECTOR parameter // is the destination structure pointer, and the STRUCTURE parameter is a // pointer to the source vector table. Note that sizeof will ensure there is // space for all functions defined in the typedef. // #define CREATE_VTABLE(VECTOR, STRUCTURE, TYPEDEF) \ VECTOR = (TYPEDEF*)Allocate(sizeof(TYPEDEF)); \ memcpy(VECTOR, &STRUCTURE, sizeof(TYPEDEF)) /* If you want to replace the macro with a function, here is the required prototype... void CREATE_VTABLE(void** to, void* from, int size); */ //----------------------------------------------------------------------------- // Free the vtable heap memory based on usage. Both parameters are pointers. // extern void DestroyVtable(void*, int*); //----------------------------------------------------------------------------- // Allocate space from the heap. // extern void* Allocate(int); //----------------------------------------------------------------------------- // Return the memory to the heap. // extern void Free(void*); //----------------------------------------------------------------------------- // Return the current count of allocated items on the heap. Call when the // process completes to check if all classes have been correctly shutdown. // extern int DataCount(void); #endif // _C_CLASSES_H
Listing Two
/****************************************************************************** PROJECT C Classes NAME C_Classes.c PURPOSE The C_Classes vtable heap allocation and de-allocation methods. AUTHOR Ron Kreymborg, Jennaron Research ******************************************************************************/ #include <stdio.h> #include "C_Classes.h" static int mDataCount; //----------------------------------------------------------------------------- // Destroy the vtable based on whether the instance count goes to zero. // void DestroyVtable(void* structure, int* count) { if (--(*count) == 0) { Free(structure); } } //----------------------------------------------------------------------------- // Remove a heap structure. // void Free(void *p) { mDataCount--; free(p); } //----------------------------------------------------------------------------- // Allocate the requested bytes from the heap. // void *Allocate(int size) { void *p = malloc(size); if (p) { mDataCount++; return p; } else { // The heap space is exhausted. Provide an error handler here. Do NOT // ever return from here unless the heap has been enlarged. Put a // suitable error handler here that matches the processor environment // requirements. // printf("Out of heap memory"); while(1) {;} } } //----------------------------------------------------------------------------- // Return the number of outstanding heap allocations. // int DataCount(void) { return mDataCount; }
The vtable Copy Macro
I try not to use macros very often, but for the vtable,
copiers save typing a lot of fiddly and error-prone syntax. The definition in the C_Classes header is:
#define CREATE_VTABLE(VECTOR, STRUCTURE, TYPEDEF) \ VECTOR = (TYPEDEF*)Allocate (sizeof(TYPEDEF)); \ memcpy(VECTOR, &STRUCTURE, sizeof(TYPEDEF))
It allocates space on the heap for the structure, assigning it to the VECTOR
pointer, then copies the class vtable
instance STRUCTURE
from ROM to this space in the heap.
Data Hiding
At this point, C++ programmers are grumbling that I have exposed the class's data by publishing the MotorData
structure in the header. This is true. There is nothing to stop users of this class typing a statement like:
newSpeed = MotorData->speed;
rather than calling the appropriate method, and so blowing away the very idea of objects. For example, what if the Motor
class provided a revolutions-per-minute interface but internally used radians-per-second? To derive from a base class, it must somehow publish its data structure. However, an ordinary class need not. You simply move the data definition into the code file and replace all exposed references to the data structure with void pointers. From the examples so far, the SetSpeed
definition in the header would then look like:
void (*SetSpeed)(void*, int);
and the example constructor, method call, and destructor would look like:
void* MotorData = MotorCtor(); Motor->SetSpeed(dMotor, 450); MotorXtor(dMotor);
Externally, the data pointer is now useless, but within the code file, a simple macro is used to cast the void
pointer. The source-code listings (available electronically) all include the ME macro for reference. Its use in the aforementioned SetSpeed
method would be:
void SetSpeed(void* d,int speed) { ME(d)->mSpeed = speed; }
Of course, a little source-code sticky-nosing would allow any programmer to learn the class data structure, but I am assuming that those using these methods would not willfully bypass their intent, and using void pointers makes accidental use very unlikely. The downside is you lose the structure cross-checking capability of the compiler.