views:

511

answers:

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.

+4  A: 

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
EFraim - Could you describe GObject implementation strategy in your answer so we can compare it to some of the other solutions given?
SetJmp
A: 

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.

cheez
Do you know if they are using an implementation like the one suggested by Dale and Falaina? Or something a little more dynamic?
SetJmp
Yep, very similar.
cheez
A: 

Which subset of OO do you need?

h0b0
classes, methods. subclasses implementing the same interface would be nice. If there are other portions of OO that have been done they would be great to document here, too.
SetJmp
+5  A: 

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
+1  A: 

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
Thanks Falain (I upvoted). Could you point to a library that does this so I or anyone else could examine a whole implementation?
SetJmp
A: 

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
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
Sigh... s/bit union/big union/ in the above. Sorry for the typo.
Dale Hagglund
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
+1  A: 

You may be interested in checking out the answers to my SO question.

Anthony Cuozzo
yes; highly relevant. Thanks!
SetJmp