OOP in C, implicitly pass self as parameter

I can't wrap my head around how to structure my "object" so instead of

Stopwatch->tick(Stopwatch);

I can write Stopwatch->tick();

This is not possible in standard C. You need to pass the receiver as an explicit formal argument to your C functions (in contrast with C++ which has this as an implicit formal).

However:

  • you generally want to pack all the method functions in one single struct with several function members (and have each instance start with a pointer to that struct). Read about vtable-s.

  • you could have some macro (or perhaps inline function) to avoid giving Stopwatch twice; you'll still write TICK(Stopwatch) not Stopwatch->tick();; the statement-expr extension of GCC could be useful.

Look into GTK and its Gobject system as an example of a cute object system for C. Read also about the ObjVLisp model and wikipage on virtual method tables. Maybe see this draft report and RefPerSys and also the blog of the late J.Pitrat.

BTW, you could decide that you have first class method selectors (perhaps as integers, or pointers to some common selector type) and code a variadic send dispatching function (so you would code send(StopWatch,TICK_SEL) instead of your dreamed Stopwatch->tick()) or macro. You might find libffi useful. The old Xview could be inspirational.

At last, as many fancy object layer implementors, you might use some metaprogramming and provide some C code generating tool (like moc in Qt). You might even consider customizing your GCC compiler with MELT for such purposes. Or making a translator (see this) from your fancy OOP dialect to C (like VALA or SWIG or Bigloo or Chicken-Scheme do; see also this). Or preprocess your code with an external preprocessor (your own one, or m4 or GPP, etc...).


Why I never liked that book much, is that tries to turn C into C++. Everyone must first realize that C++ programming is not necessarily the same as object-oriented programming. OOP is a way to do program design and it is quite unrelated to language syntax. C++ just makes it easier and prettier, is all. But just because C++ has a feature that makes code prettier in some situation, it doesn't necessarily mean that the feature is at all related to OOP (take for example operator overloading).

So don't try to turn C into C++. Accept that C has different syntax, which may not be as pretty. C actually has lots of functionality available that lets you implement OOP design. True encapsulation with private/public variables or functions is 100% achievable in C.

Since C is not C++, you don't want member functions inside the struct. The only function pointer you'll want there are special cases such as callback functions and similar. So instead of Stopwatch->tick(&Stopwatch), it is better not to use function pointers at all, but to call the member function directly: sw_tick(&Stopwatch). Where sw is some unique prefix for the stopwatch module.

This allows you to implement Stopwatch as an object of incomplete type (also called "opaque type"), which is the very core of OOP in C. Incomplete type allows you to hide the contents of the struct to the caller.

Then rewrite the whole stopwatch "class" (call it class or ADT or whatever) as something like this:

stopwatch.h

typedef struct stopwatch_t stopwatch_t; // incomplete type

stopwatch_t* sw_new (void);             // "constructor"

void sw_delete (stopwatch_t* sw);       // "destructor"

void sw_tick (const stopwatch_t* sw);   // public member function
// any number of public functions here
// mind const correctness!

stopwatch.c

struct stopwatch_t        // implementation
{
  // true private variables:

  unsigned int milliseconds;
  unsigned int seconds;
  unsigned int minutes;
  unsigned int hours;
  bool is_enabled;
};

stopwatch_t* sw_new (void)
{
  // same as what you already have
}

// the module is responsible for cleaning up its own mess, NOT THE CALLER
void sw_delete (stopwatch_t* sw)
{
  free(sw);
}

// any number of public member functions:
void sw_tick (const stopwatch_t* sw)
{
  // here sw is the "self"/"this" pointer
}

// any number of private member functions:
static void sw_do_stuff (stopwatch_t* sw)
{
}

The caller will only be able to declare pointers to objects, but never an instance of them. That's no big deal, lots of C and C++ libraries work that way. A pointer to incomplete type is somewhat similar to a pointer to an abstract base class in C++. You can't declare instances of those either.

If you need to mix private and public member variables, you would typedef a struct in the h file where the public member variables are declared as plain struct members, and the private member variables are declared through an incomplete type.


Note: there are already a number of good answers, which explain why the "method call" syntax is not available in C, however they do not explain what to do instead but just point at resources. Basic OO in C is actually relatively simple, so here is a quick HOW TO.

This HOW TO is separated in two sections:

  • the first section shows how to achieve encapsulation
  • the second section shows how to layer late binding on top

Encapsulation

Often times, OO is actually used to mean encapsulation. The idea of encapsulation is to obtain a modular design with well defined interfaces over the state of the program in the hope of making it easier to maintain invariants.

In C, this is traditionally achieved through opaque pointers:

// stop_watch.h
typedef struct stop_swatch_ stop_watch;

stop_watch* stop_watch_create();
stop_watch* stop_watch_clone(stop_watch const* sw);
void stop_watch_dispose(stop_watch* sw);

void stop_watch_tick(stop_watch* sw);
void stop_watch_start(stop_watch* sw);
void stop_watch_stop(stop_watch* sw);
void stop_watch_reset(stop_watch* sw);

This header is the only thing the user sees, and therefore it cannot name the internals of the struct stop_watch_. Of course, this being C, the user can still mess with them, but at least we made it a bit harder on them.

Note: the .c is left as an exercise to the reader; it's plain boring C code after all.

Late Binding

Late Binding is deciding at run-time which function to call; it can for example be achieved through virtual methods in C++, Java, ...

It can be done in C, with relative ease, as well. You will just not benefit from all the sugar.

// stop_watch.h
typedef struct stop_watch_functions_ stop_watch_functions;

typedef struct {
    stop_watch_functions const* functions;
} stop_watch;

struct stop_watch_functions_ {
    void (*clone)(stop_watch const*);
    void (*dispose)(stop_watch*);

    void (*tick)(stop_watch*);
    void (*start)(stop_watch*);
    void (*stop)(stop_watch*);
    void (*reset)(stop_watch*);
};

stop_watch* stop_watch_clone(stop_watch const* sw);
void stop_watch_dispose(stop_watch* sw);

void stop_watch_tick(stop_watch* sw);
void stop_watch_start(stop_watch* sw);
void stop_watch_stop(stop_watch* sw);
void stop_watch_reset(stop_watch* sw);

Alright, so we define:

  • a v-table: stop_watch_functions
  • a struct to hold onto that v-table: stop_watch; it is meant to be part of the instance of the concrete stop-watch.

Let's move on to the implementation:

// stop_watch.c
stop_watch* stop_watch_clone(stop_watch const* sw) {
    return (*sw->functions->clone)(sw);
}

void stop_watch_dispose(stop_watch* sw) {
    return (*sw->functions->dispose)(sw);
}

void stop_watch_tick(stop_watch* sw) {
    return (*sw->functions->tick)(sw);
}

void stop_watch_start(stop_watch* sw) {
    return (*sw->functions->start)(sw);
}

void stop_watch_stop(stop_watch* sw)  {
    return (*sw->functions->stop)(sw);
}

void stop_watch_reset(stop_watch* sw) {
    return (*sw->functions->reset)(sw);
}

Pretty straightforward, right?

And finally, let's move on to a concrete stop-watch implementation:

// my_stop_watch.h
#include "stop_watch.h"

typedef struct my_stop_watch_ my_stop_watch;

my_stop_watch* my_stop_watch_create();

stop_watch* my_stop_watch_upcast(my_stop_watch* msw);
my_stop_watch* my_stop_watch_downcast(stop_watch* sw);

Okay, the header is boring; all the good stuff in hidden away after all:

// my_stop_watch.c
#include "my_stop_watch.h"

struct my_stop_watch_ {
    stop_watch base;

    unsigned int milliseconds;
    unsigned int seconds;
    unsigned int minutes;
    unsigned int hours;

    bool is_enabled;
};

static stop_watch* my_stop_watch_clone(stop_watch const* sw) {
    my_stop_watch* new = malloc(sizeof(my_stop_watch));
    memset(new, (my_stop_watch const*)sw, sizeof(my_stop_watch));
}

static void my_stop_watch_dispose(stop_watch* sw) {
    free(sw);
}

static void my_stop_watch_tick(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static void my_stop_watch_start(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static void my_stop_watch_stop(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static void my_stop_watch_reset(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static stop_watch_functions const my_stop_watch_table = {
    &my_stop_watch_clone,
    &my_stop_watch_dispose,

    &my_stop_watch_tick,
    &my_stop_watch_start,
    &my_stop_watch_stop,
    &my_stop_watch_reset
};

my_stop_watch* my_stop_watch_create() {
    my_stop_watch* msw = malloc(sizeof(my_stop_watch*));

    msw->base = &my_stop_watch_table;

    /* do something */

    return msw;
}

stop_watch* my_stop_watch_upcast(my_stop_watch* msw) {
    return &msw->base;
}

my_stop_watch* my_stop_watch_downcast(stop_watch* sw) {
    if (sw->functions != &my_stop_watch_table) {
        return NULL;
    }

    return (my_stop_watch*)((char*)sw - offsetof(my_stop_watch, base));
}

Here I used the strategy of most C++ implementations (with a virtual table); there are other strategies available, but this one is widely applicable.

Tags:

C

Oop