C++ provides facilities for using objects in programs -- collections of data and functions (or methods) that are applied to them. C, and therefore, C--, require a little more ingenuity and some enforced consistency to provide a similar method of programming.
The method described here does not cater for the more complicated aspects of object oriented programming -- overloading functions, inheritence or polymorphism, for example -- largely because of the restrictions imposed by C--. It would be possible, although messy, to do so in C. Suggestions for overcoming the limitations of the approach are suggested at the end of the document.
All struct-object names should start with a capital letter. Multiple words should be concatenated, without underscores, but each word capitalised.
Public variables within a struct should start lower case, but subsequent words should be capitalised.
Private variables within the struct should begin with an underscore.
Example:
struct SimpleExample
{
int _privateVariable;
int publicVariable;
};
Do not create an instance of the struct-object at the same time as defining it.
All functions should be prefixed with the name of the struct-object.
Public functions should have one underscore between the struct-object name and the method name; private functions should have two.
Method names should begin lower case and subsequent words be capitalised.
The first parameter should be a pointer to the struct-object that the method is being called upon.
Examples:
void SimpleExample_publicMethod(struct SimpleExample* c, int x, int y);
void SimpleExample__privateMethod(struct SimpleExample* c);
There are three functions that all struct-objects should implement:
struct ObjectName* ObjectName_new(void);
void ObjectName_destroy(struct ObjectName *o);
void ObjName_copy(const struct ObjName* from, struct ObjName* to);
These are discussed in detail below, with examples making use of the following struct-object:
struct Linked
{
int _x;
double _y;
struct SimpleExample* _always; /* this always points to an object */
struct SimpleExample* _sometimes; /* this might point to an object,
or to NULL */
struct Linked* _next; /* points to next item in the list or NULL */
};
This allocates memory for a new instance of the struct-object (using malloc), and returns a pointer. Suitable default values for all variables within the struct-object should be set.
If the memory cannot be claimed (malloc returns NULL) then Error_fatal(Error_MemoryAlloc, "SimpleExample_new(void)") should be called, and NULL returned (although Error_fatal may terminate the program).
Example:
struct Linked* Linked_new()
{
struct Linked* link;
link = (struct Linked*) malloc(sizeof(struct Linked));
if (link == NULL)
{
Error_fatal(Error_MemoryAlloc, "SimpleExample_new(void)");
return NULL;
}
link->_x = 0;
link->_y = 0.0;
link->_always = SimpleExample_new();
link->_sometimes = NULL;
link->_next = NULL;
return link;
}
This frees the memory used by the struct-object passed in.
By default, any struct-objects pointed to within the destroyed object should be destroyed too, along with any other claimed memory. The only exceptions to this are pointers used in linked lists -- these should remain undestroyed, since the lists should be handled and destroyed by the main program.
Remember to destroy the values within the struct-object before freeing the struct-object itself, as otherwise you may be accessing unclaimed memory.
Also check that the object passed in is not a NULL pointer. If it is, simply return from the function.
Example:
void Linked_destroy(struct Linked* link)
{
if (link != NULL)
{
SimpleExample_destroy(link->_always);
SimpleExample_destroy(link->_sometimes);
/* we don't need to worry about destroying a NULL pointer here
since the SimpleExample_destroy function should check for it */
}
return;
}
This copies the values FROM the FIRST object TO the SECOND.
Do not copy pointers within the object, copy the struct-objects that are being pointed to. This can be done by using further _copy commands. The only exception to this is if a pointer is being used for a linked list, in which case the pointer itself should be copied.
Be careful of copying between NULL pointers within the struct-object. If copying TO a null pointer, create a new instance of the inner object and set the destination pointer to it. You can then use a _copy to transfer the data. If copying FROM a null pointer, make sure to _destroy the destination first, before setting it to be null. This avoids memory leaks.
Example:
void Linked_copy(const struct Linked* from, struct Linked* to)
{
to->_x = from->_x;
to->_y = from->_y;
SimpleExample_copy(from->_always, to->_always);
if (from->_sometimes == NULL)
{
/* from's _sometimes doesn't point to an object, so we don't
want one inside _to. */
SimpleExample_destroy(to->_sometimes);
to->_sometimes = NULL; /* now copy the NULL pointer */
}
else
{
/* from's _sometimes points to an object. */
if (to->_sometimes == NULL) /* ensure there is an */
to->_sometimes = SimpleExample_new(); /* object to copy TO */
SimpleExample_copy(from->_sometimes, to->_sometimes); /* copy */
}
}