Object-Oriented C: A Primer

By Artyom Bologov

IMAGE_ALT
Beware of your inheritance hierarchy eating up your memory... unless you use OOP C. "The Big Fish Eat the Little Fish" by Pieter van der Heyden, 1558

C is a marvelous language in how it can be small, efficient, pretty, and ugly, all at the same time. But what it lacks (driving modern developers to despair) is Object-Oriented Programming (OOP) facilities. Yes, there's C++, but one doesn't talk about C++ in good social circles. Especially when it's possible to simulate classes, objects, and methods in C. Yes, it'll look slightly irregular, but bear with me.

This post is structured around Object-Oriented Programming concepts. Starting from the simplest ones and slowly increasing the difficulty level. Be prepared: OOP is quite a fuzzy set of ideas. I'll exploit this fuzziness as much as possible—to stay within what people call OOP while not parting with C and its blessed ways. As a spoiler: the resulting system will be a Generic-based Single Inheritance one.

Here's a problem domain we're going to model: animals. There are animals (yes, a classic OOP example). Many families and species of animals. I have a lovely cat named Kalyam, so I'm mostly interested in Feline.

\
Inheritance hierarchy of an arbitrary sample of animals I am familiar with

I'm going to model just that part of biological hierarchy. Hopefully, we'll have enough material to crash-test the approach I suggest.

Encapsulation

This one is easy. In its simplest definition, encapsulation is putting things into their buckets (called classes). Encapsulation might also mean hiding data inside classes, but see visibility section for that instead. Encapsulation might also mean belonging of behavior (methods) inside classes. But that's debatable: some languages have a generic-oriented OOP where methods are freestanding entities. If anything, methods belong to their generic functions there. That's the approach I'll use. C (since C11) has generic dispatch, so why not use the feature that's already there?

Putting the generics talk aside for a second, let's encapsulate some data, shall we?

struct animal {
        char *name;
        char *species;
};
Animal structure definition listing useful data about it

That's it, we've got our parent class ready. The data is contained within, so we've got encapsulation. What we don't yet have are animal classes, orders, genera, species, etc. So let's create an extinct animal that can forgive me my poor biology knowledge:

struct animal oldie = {.name = "Oldie", .species = "Miacid"};
printf("This really old animal is %s of %s specie\n", oldie.name, oldie.species);
Animal use example

In case you don't like this struct talk, you can always define a macro and a type alias (capitalized, to please the Java crowd):

 #define class struct
typedef class animal Animal;
OOP macro and type we are going to use later

That's all there is to encapsulation, really.

Inheritance

With encapsulation out of the way, enter inheritance. A way for classes to depend on each other and share behaviors/data.

A trick I learned (from the "Good Taste" series of posts) just a few days ago: embedding structures inside each other. Putting one structure as the first member of another, you're making the outer structure castable to the inner one, sharing the behavior between the two:

typedef class carnivoire {
        Animal parent;
} Carnivoire;
Carnivoire class definition

Now we can cast any animal to (Animal *) and invoke Animal methods:

Carnivoire sabre_tooth = {{.name = "Diego", .species = "Dinictis"}};
eats((Animal *)&sabre_tooth);
Example of Carnivoire use

No, wait our animals don't know how to eat yet! Let's teach them—with polymorphism!

Polymorphism

There's a default behavior to animals: they eat (duh). That's why the diagram above includes the eats() method: any animal class eats something. However, there are all sorts of animals. Some eat plants. Some eat fungus. Some eat other animals (huh, a recursion?) Knowing that the creature is an animal, it's hard to tell what they eat. Here's how we express it with code:

void animal_eats (Animal *self)
{
        printf("%s eats ???\n", self->name);
}
 #define eats(animal)
_Generic((animal),
         default: animal_eats)
((animal))
Polymorphic eats() method implemented as a macro

For now, our eats() macro/generic only has one default method: animal_eat. But you can already see how one can extend it with just another line of type+method. Let's actually do that:

void carnivoire_eats (Carnivoire *self)
{
        printf("%s eats meat (a shame—it involves killing other animals)\n", self->parent.name);
}
 #define eats(animal)
_Generic((animal),
         Carnivoire *: carnivoire_eats,
         default: animal_eats)
((__VA_ARGS__))
Carnivore eats() method

Only one more line in the generic, and we have carnivoire-specific behavior! That's what polymorphism's promise is: specifying behavior given a type.

eats(&sabre_tooth); // Diego eats meat...
Using carnivoire-specific eats() method

Visibility

Most OOP systems have private/public/protected differentiation. I can easily cast it aside based on the fact that e.g. Python doesn't have visibility as a concept. But I'll try to implement it anyway.

The trick is treating structures as opaque data. I mean, the user of the code doesn't have to know the data layout of the structure. They have to use it as a raw pointer, relying on extern-ed functions anyway. This is exploited by many codebases. They tend to hide the pointer to the "private" version of the structure, nested inside the "public" one. WebKitGTK does this:

class WebKit2.WebViewBase : Gtk.Container
  implements Atk.ImplementorIface, Gtk.Buildable {
  priv: WebKitWebViewBasePrivate*
}
WebKit WebView private structure example

Relying on this tradition, we can say that structures are private by default. What's public is their getters and setters. So why not define some getters and setters for our structures?

char *animal_get_name (Animal *self)
{
        return self->name;
}
void animal_set_name (Animal *self, char *name)
{
        self->name = name;
}
Example getter/setter for animals

This is pretty boring, so let's define a new class with private fields and methods:

 #define private
typedef class feline {
        Carnivoire parent;
        private bool claws_out;
} Feline;
// Don't have to define name getter/setter: animal has it already.
bool feline_get_claws_out (Feline *self)
{
        return self->claws_out;
}
void feline_protract_claws (Feline *self)
{
        self->claws_out = true;
}
void feline_retract_claws (Feline *self)
{
        self->claws_out = false;
}
Feline (cat-like) class with special behavior for claws

Notice that we don't have a setter for claws_out—retract/protract methods handle modification. An important OOP technique of hiding the actual data behind the behavior.

Using the OOP system

The code so far was pretty simplistic, and there wasn't much OOP. This section and example will finally put the system to the test. Let's define cats (I've been waiting for this!) and their behaviors:

typedef class cat {
        Feline parent;
} Cat;
void cat_purr (Cat *self)
{
        printf("%s purrs...\n", animal_get_name(self));
        feline_retract_claws(self);
}
void cat_eats (Cat *self)
{
        printf("%s eats mice\n", animal_get_name(self));
}
 #define eats(animal)
_Generic((animal),
         Carnivoire *: carnivoire_eats,
         Cat *: cat_eats,
         default: animal_eats)
((animal))
Cats and their methods

Testing this system yields:

// My little sweet boy
Cat Kalyam = {{{{.name = "Kalyam", .species = "Felis catus"}}, .claws_out = true}};
printf("%s's claws are %stracted\n",
       animal_get_name(&Kalyam),
       (feline_get_claws_out(&Kalyam) ? "pro" : "re"));
// Kalyam's claws are protracted
eats(&Kalyam);
// Kalyam eats mice
cat_purr(&Kalyam);
printf("%s's claws are %stracted\n",
       animal_get_name(&Kalyam),
       (feline_get_claws_out(&Kalyam) ? "pro" : "re"));
// Kalyam's claws are retracted
Actually using the system

These nested curly brackets are not looking right, begging for constructor methods. But this post is too long already, so let's leave it for later. What's important: Encapsulation, Inheritance, Polymorphism, and Visibility are there. C can do OOP. And it's not that hard really—what this post covers is quite simple and easy to scale.

You can look at the final code (compiles with GCC and Clang, even if with heaps of warnings) in oop-c-primer-original.c. And the cleaned-up (and slightly extended with bird-specific details if you wanted to see a more involved inheritance hierarchy) code in oop-c-primer-cleanup.c. I hope that you're convinced OOP is possible in C, for better or worse. Thanks for accompanying me on this journey!

Acknowledgements

I owe a thanks to

Leave feedback! (via email)