dcsimg

Object Oriented Programming in C

When many people hear the words "Object Oriented," they think of languages such as Java, C++, or Python. Not many people think of C. This is unfortunate. However, C is a perfectly good language for most object-oriented programming, except that it lacks the syntactic sugar that is possessed by most languages designed for object-oriented programming. But what C lacks in syntax, it makes up for in flexibility (and, unfortunately, complexity).

When many people hear the words “Object Oriented,” they think of languages such as Java, C++, or Python. Not many people think of C. This is unfortunate. However, C is a perfectly good language for most object-oriented programming, except that it lacks the syntactic sugar that is possessed by most languages designed for object-oriented programming. But what C lacks in syntax, it makes up for in flexibility (and, unfortunately, complexity).

Learning to use object oriented techniques in C will not only make it easier to write your own objects in C, it will make it easier to understand the many toolkits and libraries that use these concepts. One such example is the GTK+ graphical interface toolkit — the toolkit that the GIMP and GNOME are both based on. However, even some older libraries such as Xt and Xlib are written in C, but are very object oriented.

Object Oriented Programming

So let’s start with the million dollar question: What is object oriented programming?

Object-oriented programming (OOP) is a technique that is based upon the idea of modeling the data you intend to manipulate in a way that most accurately reflects the entity you are trying to represent. After modeling the data, a set of actions (called methods or functions) can be defined that allow you to control the various ways that the data can be manipulated.

This is probably easier to illustrate with a real-word example. Let’s imagine that you want to create an object representing a Lamp. This lamp can be in one of two states; on or off. Our object would store a single bit of data indicating the state the lamp is in. Likewise, there are two actions that can be performed on this lamp — you can turn it on if it’s off, or turn it off if it’s on.

One advantage of OOP is that it allows the programmer to hide the internal implementation of an object (in this case the lamp) from the outside world. All the world needs to know about are the “methods” or functions that can be used to manipulate the object (lamp). No one needs to know how we internally represent the lamp’s state. They only need to know that the lamp is either on or off and how they can change that state if they want to.

So, now we’re ready to look at some OOP terms. The main data structure in OOP is called a “class.” Think of a class as an object factory. A class defines a type of object and is a template from which we can manufacture “objects” of a given type. Put another way, an object is a specific instantiation of a given “class.”

Here again, an example may clear things up. Your actual lamp (let’s name it “foo”) is an object. “Foo” is an object of type Lamp. This means that the object “foo” is just a specific instance of the general class Lamp.

As we said earlier, objects contain data that can be manipulated via “methods” (sometimes called “member functions” — as in “members of this object”). Methods define the actions that can be performed on the object’s data. In the case of our lamp, there is a single boolean variable that represents the state (on or off) of the lamp. That variable is our data in this case.

A Simple Class

So, let’s implement this in C. Most of the time an object is just a simple structure. So the code shown below defines a Lamp object.


typedef struct {
int state; /* TRUE if on, FALSE if off */
} Lamp;

void
lamp_turn_on(Lamp *lamp)
{
lamp->state = TRUE;
}

void
lamp_turn_off(Lamp *lamp)
{
lamp->state = FALSE;
}

As you can see, in C a method is simply a function that takes the pointer to our object as its first argument. We also prepend lamp_ to the method / function name to make it clear that these are methods belonging to the lamp class. The above example is missing something though — a way to create and destroy the objects of type lamp. So below we define a new and a destroy method.


Lamp *
lamp_new(void)
{
Lamp *lamp = malloc(sizeof(Lamp));
lamp->state = FALSE;
return lamp;
}

void
lamp_destroy(Lamp *lamp)
{
free(lamp);
}

In this case, these functions are all very simple and may seem like overkill, but remember that we have made the lamp class very simple for the sake of example.

Dynamic Type Checking

Now, let’s say that we have several different classes of objects and that we need a way to determine at runtime that a particular function is working with the right type of object. We will need to add a type field to our class. The easiest way to do this is by defining a unique integer for each class. So let’s say we have a #define in our header that assigns this type of ‘lamp’ the integer value 55. That would look like this:


#define LAMP_TYPE 55

Then we redefine our Lamp structure as follows:


typedef struct {
int type;
int state; /* TRUE if on, FALSE if off */
} Lamp;

Now in the new method we would initialize the type.


Lamp *
lamp_new(void)
{
Lamp *lamp = malloc(sizeof(Lamp));
lamp->type = LAMP_TYPE;
lamp->state = FALSE;
return lamp;
}

The example below contains the destroy method. Now the destroy method (and all the other methods of course) can do some type checking. We will use assert(0) if the object happens to fail the type check. This requires you to use the #include <assert.h> header file in your program. This will cause the program to abort and dump core so that you can debug it if the object fails the type check. Depending on your program there may be more graceful ways to recover.


void
lamp_destroy(Lamp *lamp)
{
/* check if we have a valid lamp object */
if(lamp == NULL ||
lamp->type != LAMP_TYPE)
assert(0);
/* set type to 0 indicate this is no
longer a lamp object */
lamp->type = 0;
/*free the memory allocated for the object*/
free(lamp);
}

If you notice, we also reset the type field to 0 in the destroy method. This is so that if you make a mistake and call lamp_destroy() twice, the program aborts letting you debug the problem.

Reference Counting

One of the problems with C is that it is incumbent upon the programmer to manually insure that memory is being properly allocated and de-allocated. Some languages take care of this chore for you. There are actually quite a few different techniques for dealing with the problem of managing memory, but we’ll look at the simplest one — “reference counting.”

To implement reference counting, each object will contain an integer that counts the number of places that reference, or have a pointer to, the object. You don’t want to destroy and free the object’s memory as long as there are places in your program that reference that object. So you only want to destroy the object when the “reference count” drops to 0.

Reference counting can be implemented with one of two methods. One is called ref and is used to increase the reference count of the object. The other, called unref, decreases the reference count. If the reference count is 0, we will then call the lamp_destroy() function and destroy the object.

Thus, the lamp_new() method should create the object and give it a reference count of 1. This is because the place that called the new method now holds a reference to the object. An example of how to implement this is contained below (we’ve left out the type-checking stuff from above to keep things simple):


typedef struct {
int ref_count; /* the reference count */
int state; /* TRUE if on, FALSE if off */
} Lamp;

Lamp *
lamp_new(void)
{
Lamp *lamp = malloc(sizeof(Lamp));
/*initialize the ref_count to one reference*/
lamp->ref_count = 1;
lamp->state = FALSE;
return lamp;
}

static void
lamp_destroy(Lamp *lamp)
{
free(lamp);
}

void
lamp_ref(Lamp *lamp)
{
lamp->ref_count++;
}

void
lamp_unref(Lamp *lamp)
{
lamp->ref_count–;
if(lamp->ref_count == 0)
lamp_destroy(lamp);
}

Unfortunately, this scheme has problems. The biggest one is that there is the possibility of having circular references. What’s a circular reference? Imagine two objects containing pointers to each other. If all the places outside of those two objects had already called unref, the objects would still not get destroyed, as object A still holds a reference to object B, while object B still holds a reference to object A.

This is a serious flaw which limits the flexibility of programs designed with reference counting. On the other hand, reference counting is frequently used just because it is very simple to implement, and it is an extremely efficient way to manage memory. For most practical purposes, it is usually easy to design your code to avoid circular references.

Single Inheritance

So this is all nice and dandy, but the real power of object- oriented design is in inheritance. Implementing inheritance in C isn’t as obvious as the previous techniques were, but it is not very hard. To derive a class, what you need is a new structure that begins with the exact same fields as the parent structure. This allows you to cast the new structure to the same type as the parent structure and still call the methods of the parent class as well as the methods of the new class.

So let’s go ahead and define a new class called TableLamp. This is a rotating lamp that can also swivel to a certain angle. We define this structure by making the first field the Lamp structure. This way the first couple of bytes of the TableLamp structure will be identical to the Lamp structure. Here it is:


typedef struct {
Lamp parent;
int angle;
} TableLamp;

We will also need to define another new method which creates the TableLamp object, and perhaps a rotate method which rotates the lamp by a specified number of degrees. However, we now have a problem. In our design of the Lamp class, the only place where the object gets constructed is the lamp_new() function. This means we would have to copy the construction code over to our table lamp object. That is just not nice. Not only is that bad coding practice, but it is also a disaster waiting to happen. The only way to guarantee that code in two different places is in sync is not to have it in two places in the first place. Our Lamp class example is simple, but most real world object will be much more complex.

The way to get around this is to set up a method in the Lamp class called construct that will take a newly allocated pointer to a Lamp object and construct it. Our first example (below) contains the code for doing this:


typedef struct {
int state; /* TRUE if on, FALSE if off */
} Lamp;

Lamp *
lamp_new(void)
{
Lamp *lamp = malloc(sizeof(Lamp));

/* call the constructor */
lamp_construct(lamp);

return lamp;
}

void
lamp_construct(Lamp *lamp)
{
lamp->state = FALSE;
}

Whereas our second example (below) contains the code for the resultant TableLamp class:


typedef struct {
Lamp parent;
int angle;
} TableLamp;

TableLamp *
table_lamp_new(void)
{
TableLamp *lamp = malloc(sizeof(TableLamp));

/* call our constructor */
table_lamp_construct(lamp);

return lamp;
}

void
table_lamp_construct(TableLamp *lamp)
{
/* call the constructor of the ‘Lamp’class */
lamp_construct((Lamp *)lamp);

lamp->angle = 0;
}

Notice that we call the constructor of our parent object in our own constructor. This way we don’t have to worry about any further parents of Lamp — their constructors will get called by the Lamp class itself. This also demonstrates how to call a method of the Lamp class with an object of type TableLamp.

Inheritance vs. Dynamic Type Checking

You may have noticed that the runtime type checking we described earlier will no longer work correctly. This is because when you call a method of the Lamp class with an object of type TABLE_LAMP_TYPE, the type checking code will fail. There are many ways to design a type checking system that survives inheritance. We’ll look at a very simple method for doing this here.

Let’s say that we want to be able to recognize an object of class TableLamp as both TABLE_LAMP_TYPE and LAMP_ TYPE. This is a natural place to use specific bits in the type integer to mark the different types. Let’s say that the lowest bit means Lamp and the second bit means TableLamp. Thus we define our type constants as follows:


#define LAMP_TYPE (1<<0)
#define TABLE_LAMP_TYPE (1<<1)

Further types can be added by increasing the second number in the expression (thus shifting the bit further left). It is worth pointing out that you can only have as many types as there are bits in the type field. If you make the type field an unsigned integer, then you will have at least 32 bits on any modern system. Or, if using the GNU compiler, you can use 64 bit type fields on most systems.

So, how do we set up the types? First, we set the types as usual in the new methods. However, we also want to set up all the bits of type field corresponding to our parent classes. Thus, in the constructor method, we need to OR this class’s type integer into the parent class’s type field. This will set this bit but not touch any other bits. This way, after construction is done, the object has all the correct bits set in the type field. Let’s see how this would work. The example below contains the Lamp class:


typedef struct {
unsigned int type;
int state; /* TRUE if on, FALSE if off */
} Lamp;

Lamp *
lamp_new(void)
{
Lamp *lamp = malloc(sizeof(Lamp));
lamp->type = LAMP_TYPE;

/* call the constructor */
lamp_construct(lamp);

return lamp;
}

void
lamp_construct(Lamp *lamp)
{
/* here OR the LAMP_TYPE constant into
the type */
lamp->type |= LAMP_TYPE;

lamp->state = FALSE;
}

whereas the next example contains the TableLamp class:


typedef struct {
Lamp parent;
int angle;
} TableLamp;

TableLamp *
table_lamp_new(void)
{
TableLamp *lamp = malloc(sizeof(TableLamp));
((Lamp *)lamp)->type = TABLE_LAMP_TYPE;
/* call our constructor */
table_lamp_construct(lamp);

return lamp;
}

void
table_lamp_construct(TableLamp *lamp)
{
/* here OR the TABLE_LAMP_TYPE constant
into the type */
((Lamp *)lamp)->type |= TABLE_LAMP_TYPE;

/* call the constructor of the ‘Lamp’ class*/
lamp_construct((Lamp *)lamp);

lamp->angle = 0;
}

Now we need to check the types. Don’t forget that it won’t work to check the type in the constructor method itself, since the types are not yet set up correctly at the time the constructor is called. However, all other methods can then just use the & (logical AND) operator to see if the type field contains the correct bit. So for example, the turn_on method of the class Lamp would now look like the example below:


void
lamp_turn_on(Lamp *lamp)
{
/* check if we have a valid lamp object */
if(lamp == NULL ||
lamp->type & LAMP_TYPE)
assert(0);

lamp->state = TRUE;
}

Wrapping Things Up

However, it’s worth keeping in mind that the OOP techniques we have discussed in this article represent only the basics. Do not feel constrained by what we’ve outlined here. However, as a general tip, when working on a design for a program, keep the design of your objects as simple as possible. The more complex your objects are, the more complex the code becomes and the harder it will be to debug and maintain.

With that in mind, go crazy and have some fun with objects!



George Lebl is a freelance object-oriented programmer and writer living in San Diego, CA. He can be reached at jirka@5z.com.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62