C With Classes

No, I’m not taking an ignorant view on the nature of C++, I mean exactly that: C, with classes. “But C doesn’t have classes” you say. Technically you would be right.

One of the things I find great about C is that while it is very simple, you can build anything with it. An analogy that I’m fond of is Lego. If programming is like Lego bricks, then C is the basic tub of square bricks. They are simple, and universally useful.

Meanwhile C++, Java, C#, and whatever other fancy, “feature rich” langauge you can think of are like the specialized kits. They come with all sorts of fancy curved and bendy pieces. These pieces can be used to make some cool things but are only useful in very specific situations. If you’ve ever played with Lego, you know that the fancy kits still come with a few regular square bricks.

One might think you’d need the fancy slanted bricks to make a truly intricate model, but one would be wrong. If you scale things up, your basic square bricks can create just as intricate models as any fancy kit.

Back on the subject of programming, many large projects these days make heavy use of object-oriented features. Data is logically grouped into objects, which have behaviors. This model of programming is facilitated by “Object-Oriented” languages, which have these types of structures built into the language.

C does not have a class keyword, and classes are not built into the language. This doesn’t mean that you can’t do object-oriented programming in C.

What Constitutes a Class

Books have been written about this topic, and given the audience of a programming blog, you’ve probably already read one or two of them. The main goal of a class is to centralize related data and logic into a single unit, and to insulate the rest of the program from it. A class contains data describing what it is and what it does, and has methods that act on that data. The class keeps this data hidden as much as possible from the rest of the program. A printer class might need to keep a bunch of state data, but the rest of the program doesn’t need to know about that. All the rest of the program needs to know is if the printer class is ready to accept a job, and how to tell the printer class to print.

This is called separation of concerns, and it’s good because it prevents the user of a class from getting bogged down in the nitty-gritty of an operation.

Meanwhile, C has structs. A struct has no access control, and no methods. Anybody who has ever attempted to deal with something like termios know what a nightmare this can become.

I’m here to tell you that it doesn’t have to be this way.

What Could Be

What should object-oriented programming look like in C? To answer that question, let’s really break down the idea of a class. At the core, a class has three parts: methods, data, and access control. Data is state that a class maintains, methods are functions that act upon a class, and access control is how a class decides if the outside world should be able to see data or a method. All three of these things can be accomplished in C.

Data

This is the easiest part. Data can be grouped in C using a struct, and this is how we will be doing it.

Methods

Now things are getting a bit trickier. While C has function pointers, and these can be stored in a struct, this is not how we will be doing it. In most object-oriented languages, you can access a this within the body of a method to access data within the class. You can think of this as an implicit extra argument to the function. Similarly, to approximate methods within C, we will be using functions. The first argument to any “Method” should be a pointer to the data struct.

Access Control

This is accomplished by way of an “opaque struct”. That is, a struct that is forward declared in the header, but without a definition. This struct can be passed around, but its members can’t be directly accessed because they haven’t been forwardly declared. In this manner, we approximate private data. If we want our class to have public fields, we can forwardly declare a transparent struct. This struct should have a pointer to a second opaque struct that will serve as its private data.

A Contrived Example

This is all a lot clearer with an example. First let’s take a look at the header for class contrived:

typedef struct _contrived { int first; struct c_priv * priv; } contrived; contrived * dmp_contrived_new(int first, int second, int third); void dmp_contrived_free(contrived * to_free); int dmp_contrived_sum(contrived * ths); int dmp_contrived_get_second(contrived * ths); void dmp_contrived_set_second(contrived * ths, int to_set);

First, we have our class struct. Class contrived has 1 public field, first, and a pointer to an opaque private struct. Users of this class can directly access first, but they can’t access anything within the private area.

Next we have a constructor and a destructor. If a user of the class wants to create or delete a contrived, they would not use malloc and friends, they would use the constructor and destructor. This solves another problem of C, complicated manual memory management.

After that, we have three methods: dmp_contrived_sum, a method on contrived, and a getter and setter for second, a private field. Similar to any other object-oriented language, we are free to break our encapsulation by providing getters and setters.

Now, let’s take a look at our implementation:

struct c_priv { int second; int thrid; };

First we have the definition of the private area. Since this is only in the .c file, it is not visible to the rest of the program.

contrived * dmp_contrived_new(int first, int second, int third) { contrived * working = malloc(sizeof(contrived)); working->first = first; working->priv = malloc(sizeof(struct c_priv)); working->priv->second = second; working->priv->thrid = third; return working; }

Next we have our constructor. It takes care of calls to malloc, and data initialization. When it’s done, it returns the newly created pointer.

void dmp_contrived_free(contrived * to_free) { free(to_free->priv); free(to_free); }

The destructor ensures all memory is freed. This way, a user never needs to worry about freeing data. Since this pointer has a pointer in it, it would be very easy to forget the nested pointer and leak memory. This problem is solved by taking the responsibility away from the user.

int dmp_contrived_sum(contrived * ths) { return ths->first + ths->priv->second + ths->priv->thrid; } int dmp_contrived_get_second(contrived * ths) { return ths->priv->second; } void dmp_contrived_set_second(contrived * ths, int to_set) { ths->priv->second = to_set; }

Finally, we have our contrived methods. They deal with the nitty-gritty of struct manipulation, much like traditional class members.

You may have noticed that I do not have any private methods. This is also very easy to implement; just create a static function in the same .c file. This function will not be visible outside the file it resides in, making it private.

What About Inheritance?

This is also possible in C. Basically, you can just include a pointer to a base class object in a derived class object. There also frameworks such as GObject that can facilitate this in a more robust way.

However, if you find yourself making heavy use of inheritance, I cannot advocate the use of C over a traditional object-oriented language. “Inheritance” in C makes heavy use of void pointers, and this vastly degrades the type safety of your program. While it can be done, and even done safely if you try hard enough, I feel that it is no longer worth it. Object oriented languages with these features built into them will vastly improve your productivity at this point.

But if your program just makes use of a few of your own classes, and those provided by libraries, then there’s no reason you should feel obligated to use an object-oriented language. Indeed, for basic objects, C is more than enough for anybody.

Trackbacks / Pingbacks

  1. Object Oriented Haskell | Doing My Programming... - September 18, 2014

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: