2009.08.06 - last edit >> Thu, 06 Aug 2009 22:21:59 -0400

Multiple Inheritance

I've been working on an ongoing pet project. It's had many faces thus far, but its current one is in C and was started at the beginning of summer. Including test cases and currently broken stuff, the line count is 2176. But what does the code do? Closures, data buffer, doubly linked list, and binary search tree. Sometime soon I hope to get red-black trees working as well. Enough about how little code was actually produced... as this is the fate of pet projects.

The interesting bit: Let's examine the is-a/has-a relationship. Certainly there is a difference, but is it so bold to represent an is-a relationship as a has-a one in data? Consider the following structures - a binary search tree node and a red-black tree node.

struct bstnode
{
    struct bstnode* p;
    struct bstnode* child[2];
};

struct rbtnode
{
    struct bstnode bstnode;
    bool redp;
};


Indeed, if you wanted to use a rbtnode as a bstnode, you could just cast it and vice-versa.

struct bstnode* n = (struct bstnode*)
    malloc (sizeof (struct rbtnode));

((struct rbtnode*) n)->redp = 1;


But that too got me thinking, I have this buffer:

struct closure
{
    void (* fn) (struct closure*);
};

struct buffer
{
    struct closure read_source;
    size_t eof;
    struct dlist bufitr_list;
    struct chunkq chunkq;
};


Now, I can argue that a buffer inherits from a closure and a chunkq in this context. When the buffer runs out of data and needs more, the closure is called to update it. It "inherits" from a closure because it acts as if the rest of its data members are local variables to a closure. In this next example, buffer->read_source.fn = &reader_fn

struct stream_buffer
{
    struct buffer buffer;
    FILE* stream;
};

void
    reader_fn
(struct closure* closure)
{
    struct buffer* buf = (struct buffer*) closure;

    buf->eof = fread (fillable_buffer (buf),
                      1,
                      buf->chunkq.chunk_size,
                      ((struct stream_buffer*) buf)->stream);
}


So you can see that the closure is casted to a buffer as if the buffer inherits from it. Now, inheritance in this case is a bit of a stretch, but you can see the parallels. A buffer inheriting from a chunkq is more obvious, I was originally going to make the buffer do everything the chunkq does, but I decided it would be best to modularize the chunkq-specific functions a bit.

What is a chunkq? A queue of data chunks. Each chunk is the same size. Easy enough?

Now to the point. It would be beneficial to be able to treat a buffer as both a closure and a chunkq. At present, it only shares identity with a closure out of necessity. I was pondering today the prospect of multiple inheritance using this model. For one, overloading/overriding functions need not be considered since the inheritance is purely in data. So, all that one needs to do such inheritance is a way to offset the pointer to point to the buffer's data member which is inherited from when casting to it and vice-versa when casting back from it.

Since the offset would be data member specific and not data-type specific, such a cast would look like...

struct buffer* buf = some_crap;
struct chunkq* q = (struct buffer -> chunkq) buf;
assert (buf == (struct buffer <- chunkq) q);


The only problem here is an extra step processing the code. At this point it's bordering on language extension! I've though about it, and doing this completely in the C language is ugly at best. (of course the extra processing step could be a C program)

I'll just leave it at that. Thanks for reading!

-- Alex