Guide to creating struct-objects

Introduction.

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.

Naming conventions.

Struct-objects.

All struct-object names should start with a capital letter. Multiple words should be concatenated, without underscores, but each word capitalised.

Variables within struct-objects.

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.

Struct-object functions (methods).

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);

Compulsory struct-object functions.

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 */
     };

struct ObjectName* ObjectName_new(void);

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;
     }

void ObjectName_destroy(struct ObjectName *o);

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;
     }

void ObjName_copy(const struct ObjName* from, struct ObjName* to);

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 */
       }
     }

Back to developer's page