Pedro Borges
Pedro Borges

Reputation: 85

Polymorphism in C

I'm designing a program in C that manipulates geometric figures and it would be very convenient if every type of figure could be manipulated by the same primitives.

How can I do this in C?

Upvotes: 5

Views: 3878

Answers (3)

paxdiablo
paxdiablo

Reputation: 882116

You generally do it with function pointers. In other words, simple structures that hold both the data and pointers to functions which manipulate that data. We were doing that sort of stuff years before Bjarne S came onto the scene.

So, for example, in a communications class, you would have an open, read, write and close call which would be maintained as four function pointers in the structure, alongside the data for an object, something like:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And the data for the object goes here.
} tCommsClass;

tCommsClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommsClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

The initialisation of those function pointers would actually be in a "constructor" such as rs232Init(tCommClass*), which would be responsible for setting up the default state of that particular object to match a specific class.

When you 'inherit' from that class, you just change the pointers to point to your own functions. Everyone that called those functions would do it through the function pointers, giving you your polymorphism:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Sort of like a manually configured vtable, in C++ parlance.

You could even have virtual classes by setting the pointers to NULL -the behaviour would be slightly different to C++ inasmuch as you would probably get a core dump at run-time rather than an error at compile time.

Here's a piece of sample code that demonstrates it:

#include <stdio.h>

// The top-level class.

typedef struct _tCommClass {
    int (*open)(struct _tCommClass *self, char *fspec);
} tCommClass;

// Function for the TCP class.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

// Function for the HTML class.

static int htmlOpen (tCommClass *html, char *fspec) {
    printf ("Opening HTML: %s\n", fspec);
    return 0;
}
static int htmlInit (tCommClass *html) {
    html->open = &htmlOpen;
    return 0;
}

 

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHtml;

    // Same base class but initialized to different sub-classes.
    tcpInit (&commTcp);
    htmlInit (&commHtml);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHtml.open)(&commHtml, "http://www.microsoft.com");

    return 0;
}

This produces the output:

Opening TCP: bigiron.box.com:5000
Opening HTML: http://www.microsoft.com

so you can see that the different functions are being called, depending on the sub-class.

Upvotes: 13

Friedrich
Friedrich

Reputation: 6006

I'm astonished, does no one have mentioned glib, gtk and the GObject system. So instead of baking yet-another-oo-layer-upon-C. Why not use something that has proofed to work?

Regards Friedrich

Upvotes: 1

Robert
Robert

Reputation: 6540

People have done silly things with various types of structs and relying on predictable padding - for example you can define a struct with a particular subset of another struct and it'll usually work. See below (code stolen from Wikipedia):

struct ifoo_version_42 {
   long x, y, z;
   char *name;
   long a, b, c;
};
struct ifoo_old_stub {
   long x, y;
};
void operate_on_ifoo(struct ifoo_version_42 *);
struct ifoo_old_stub s;
...
operate_on_ifoo(&s);

In this example, the ifoo_old_stub could be considered a superclass. As you can probably figure out, this relies on the fact that the same compiler will pad the two structs equivalently, and trying to access the x and y of a version-42 will work even if you pass a stub. This ought to work in the reverse as well. But AFAIK it doesn't necessarily work across compilers, so be careful if you want to send a struct of this format over the network, or save it in a file, or call a library function with one.

There's a reason polymorphism in C++ is pretty complicated to implement... (vtables, etc)

Upvotes: 0

Related Questions