7

I am especially interested in objects meant to be used from within C, as opposed to implementations of objects that form the core of interpreted languages such as python.

Svante
  • 46,788
  • 11
  • 77
  • 118
Setjmp
  • 23,834
  • 23
  • 67
  • 90
  • 1
    Although I've always used what I guess you could call an "object based" style of C programming, it is much, much easier to to this (and just about everything else) in C++. I have to ask why you don't seem to want to use it? –  Aug 04 '09 at 07:55
  • 1
    @Neil: If there would be no reason, there would be no GObject. There are many reasons for C: obsolete platforms, protability, binary ABI compatibility, you name it. – EFraim Aug 04 '09 at 08:19
  • @EFraim I'm well aware of those reasons, but I was asking what the questioner's reasons were. –  Aug 04 '09 at 08:57
  • 2
    @Niel - not wanting to inject C++ as a dependency should be reason enough. Also, there are a lot of clever solutions out there... I'd like to see them documented/compared here. – Setjmp Aug 04 '09 at 13:39

6 Answers6

9

I tend to do something like this:

struct foo_ops {
    void (*blah)(struct foo *, ...);
    void (*plugh)(struct foo *, ...);
};
struct foo {
    struct foo_ops *ops;
    /* data fields for foo go here */
};

With these structure definitions, the code implementing foo looks something like this:

static void plugh(struct foo *, ...) { ... }
static void blah(struct foo *, ...) { ... }

static struct foo_ops foo_ops = { blah, plugh };

struct foo *new_foo(...) {
   struct foo *foop = malloc(sizeof(*foop));
   foop->ops = &foo_ops;
   /* fill in rest of *foop */
   return foop;
} 

Then, in code that uses foo:

struct foo *foop = new_foo(...);
foop->ops->blah(foop, ...);
foop->ops->plugh(foop, ...);

This code can be tidied up with macros or inline functions so it looks more C-like

foo_blah(foop, ...);
foo_plugh(foop, ...);

although if you stick with a reasonably short name for the "ops" field, simply writing out the code shown originally isn't particularly verbose.

This technique is entirely adequate for implementing a relatively simple object-based designs in C, but it does not handle more advanced requirements such as explicitly representing classes, and method inheritance. For those, you might need something like GObject (as EFraim mentioned), but I'd suggest making sure you really need the extra features of the more complex frameworks.

Dale Hagglund
  • 14,948
  • 3
  • 26
  • 37
7

Your use of the term "objects" is a bit vague, so I'm going to assume you're asking how to use C to achieve certain aspects of Object-Oriented Programming (feel free to correct me on this assumption.)

Method Polymorphism:

Method polymorphism is typically emulated in C using function pointers. For example if I had a struct that I used to represent an image_scaler ( something that takes an image and resizes it to new dimensions ), I could do something like this:

struct image_scaler {
    //member variables
    int (*scale)(int, int, int*);
}

Then, I could make several image scalers as such:

struct image_scaler nn, bilinear;
nn->scale = &nearest_neighbor_scale;
bilinear->scale = &bilinear_scale;

This lets me achieve polymorphic behavior for any function that takes in a image_scaler and uses it's scale method by simply passing it a different image_scaler.

Inheritance

Inheritance is usually achieved as such:

struct base{
   int x;
   int y;
} 

struct derived{
   struct base;
   int z;
}

Now, I'm free to use derived's extra fields, along with getting all the 'inherited' fields of base. Additionally, If you have a function that only takes in a struct base. you can simply cast your struct dervied pointer into a struct base pointer with no consequences

Falaina
  • 6,435
  • 26
  • 31
  • Thanks Falain (I upvoted). Could you point to a library that does this so I or anyone else could examine a whole implementation? – Setjmp Aug 04 '09 at 13:43
4

Libraries such as GObject.

Basically GObject provides common way to describe opaque values (integers, strings) and objects (by manually describing the interface - as a structure of function pointers, basically correspoinding to a VTable in C++) - more info on the structure can be found in its reference

You would often also hand-implement vtables as in "COM in plain C"

EFraim
  • 11,975
  • 3
  • 42
  • 62
  • EFraim - Could you describe GObject implementation strategy in your answer so we can compare it to some of the other solutions given? – Setjmp Aug 04 '09 at 13:45
3

As you can see from browsing all the answers, there are libraries, function pointers, means of inheritance, encapsulation, etc., all available (C++ was originally a front-end for C).

However, I have found that a VERY important aspect to software is readability. Have you tried to read code from 10 years ago? As a result, I tend to take the simplest approach when doing things like objects in C.

Ask the following:

  1. Is this for a customer with a deadline (if so, consider OOP)?
  2. Can I use an OOP (often less code, faster to develop, more readable)?
  3. Can I use a library (existing code, existing templates)?
  4. Am I constrained by memory or CPU (for example Arduino)?
  5. Is there another technical reason to use C?
  6. Can I keep my C very simple and readable?
  7. What OOP features do I really need for my project?

I usually revert to something like the GLIB API which allows me to encapsulate my code and provides a very readable interface. If more is needed, I add function pointers for polymorphism.

class_A.h:
  typedef struct _class_A {...} Class_A;
  Class_A* Class_A_new();
  void Class_A_empty();
  ...

#include "class_A.h"
Class_A* my_instance;
my_instance = Class_A_new();
my_instance->Class_A_empty();  // can override using function pointers
Wadester
  • 343
  • 2
  • 5
0

Look at IJG's implementation. They not only use setjmp/longjmp for exception handling, they have vtables and everything. It is a well written and small enough library for you to get a very good example.

Sohail
  • 2,844
  • 1
  • 21
  • 22
  • Do you know if they are using an implementation like the one suggested by Dale and Falaina? Or something a little more dynamic? – Setjmp Aug 04 '09 at 13:49
0

Similar to Dale's approach but a bit more of a footgun is how PostgreSQL represents parse tree nodes, expression types, and the like internally. There are default Node and Expr structs, along the lines of

typedef struct {
    NodeTag n;
} Node;

where NodeTag is a typedef for unsigned int, and there's a header file with a bunch of constants describing all the possible node types. Nodes themselves look like this:

typedef struct {
    NodeTag n = FOO_NODE;
    /* other members go here */
} FooNode;

and a FooNode can be cast to a Node with impunity, because of a quirk of C structs: if two structs have identical first members, they can be cast to each other.

Yes, this means that a FooNode can be cast to a BarNode, which you probably don't want to do. If you want proper runtime type-checking, GObject is the way to go, though be prepared to hate life while you're getting the hang of it.

(note: examples from memory, I haven't hacked on the Postgres internals in a while. The developer FAQ has more info.)

Meredith L. Patterson
  • 4,513
  • 25
  • 29
  • Long long ago (circa 1988) I worked on a C compiler where the parse nodes were a bit union of individual node types, with a type tag outside the union to decide which branch of the union was valid: this is morally equivalent to what you describe. Today, I'd almost certainly do it along the lines of my suggestion above. I find that using function pointers really forces me to define the desired public api--since the caller doesn't know the name of the function it's calling, it's very hard for it to reach past that function to use internal data about the implementation. – Dale Hagglund Aug 07 '09 at 06:53
  • Sigh... s/bit union/big union/ in the above. Sorry for the typo. – Dale Hagglund Aug 07 '09 at 06:55
  • The function pointer approach is definitely superior -- for one thing, it lets you approximate binding methods to the object. I like your examples, and voted it up. – Meredith L. Patterson Aug 07 '09 at 08:26