Part II: Better

In the last section we examined how Opaque types in C can be adapted to follow some Object Oriented Design Patterns. It is a sensible approach but it still lacks memory management, collections, strings, and many other useful object-oriented design features. To continue on our path towards a more complete approach let's look how we can introduce retain count memory management.

Reference Counting

When an type is no longer needed it should be deallocated and its memory freed. But how will a type know when it's no longer needed? Type A may hold a reference (pointer) to type B, but how does type A know that type B still exists? For example, type B could have initially been created to be part of type C. If type C gets destroyed along with all it's constituent objects and type A doesn't know, then type A could end up sending a message to (calling a function with) a non-existent type B, and crash the program.

The solution we adopt to solve this problem is called reference counting. When a type wants to hold a reference to another type it calls that type's retain function. Every time an type's retain function is called, it increments its internal retainCount variable. Conversely, when an type no longer needs to hold a reference to a type it calls that type's release function. Every time an type's release function is called, it decrements its internal retainCount variable. When a type's retainCount hits zero, then the type self destructs. That is, it would call the release function of any types it had retained, and then deallocate itself.

With this in mind, we follow the conventions below.

  • if you create an type (either directly or by making a copy of another type—see “The Create Rule”), you own it. We will explicitly use the word Create or Copy in the name of any function that creates and returns a type with a retain count of 1.
  • if you get an type from somewhere else, you do not own it. If you want to prevent it being disposed of, you must add yourself as an owner (using a retain method).
  • if you are an owner of an type, you must relinquish ownership when you have finished using it (using a release method).

Read more about Reference counting at Wikipedia and at Apple.

Implementation

We begin by creating a fundamental opaque type called KFType, from which all other types will inherit. In KFType source code define structure

struct __KFType {
     u_int32_t retainCount;
     void (*finalize)(void *); 
     bool (*equal)(void *, void *);
};

In KFType header define opaque type

typedef struct __KFType * KFTypeRef;
KFType Methods
bool KFTypeEqual(KFTypeRef theType1, KFTypeRef theType2)
{
return theType1->equal(theType1,theType2);
}

void KFRelease(KFTypeRef theType)
{
     if(NULL==theType) return;
     if(theType->retainCount == 1) {
          theType->finalize(theType); return; 
     }
     theType->retainCount--;
     return;
}

KFTypeRef KFRetain(KFTypeRef theType)
{
     if(NULL==theType) return NULL;
     theType->retainCount++;
     return theType;
}
Now we can define KFShape to inherit from KFType
struct __KFShape {
     u_int32_t retainCount;
     void (*finalize)(void *); 
     bool (*equal)(void *, void *);

     // Shape Type attributes
     float xPosition; 
     float yPosition; 
     float orientation;
};

static struct __KFShape *KFShapeAllocate()
{
     struct __KFShape *theShape = malloc(sizeof(struct __KFShape)); 
     if(NULL == theShape) return NULL;
     theShape->retainCount = 1;
     theShape->finalize = KTShapeFinalize;
     theShape->equal = KFShapeEqual;
     return theShape;
}

Usage of KTShape

    KFMutableShapeRef shape = KFShapeCreateMutable(0.0, 0.0, 0.0);
    KFShapeShow(shape);
    
    KFShapeTranslate(shape, 10.0, 20.0);
    KFShapeRotate(shape, 180.);
    KFShapeShow(shape);
    
    KFRelease((KFTypeRef) shape);

Source code used in this section:

KFType.c
KFType.h
KFShape.c
KFShape.h
main.c