5

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?

Pedro Borges
  • 85
  • 1
  • 6
  • 4
    "Polymorphism in C".. is that kind of like "Tolerance in the Nazi regime"? – Ed S. Dec 16 '10 at 01:35
  • 3
    http://www.planetpdf.com/codecuts/pdfs/ooc.pdf – Chris Lutz Dec 16 '10 at 01:36
  • I know, I know. It was a joke, yeesh. Maybe not a good one, but still, a joke nonetheless =D – Ed S. Dec 16 '10 at 01:43
  • 2
    Technically, Godwin's law now means that C is the best OO language ever. Well done, @Ed Swangren, you've ruined it for everyone :P – detly Dec 16 '10 at 01:48
  • 1
    Near-duplicate of http://stackoverflow.com/questions/415452/object-orientation-in-c – Adam Rosenfield Dec 16 '10 at 01:50
  • @Adam, I'm glad you said "near" duplicate. That one called specifically for pre-processor hacks (pax shudders at the thought he may one day have to _maintain_ that code). This one is more open to other ways of doing it. – paxdiablo Dec 16 '10 at 02:19
  • @EdS. - you might want to read a bit on Jessie Owens, a black athlete who had to go to "racist Nazi Germany" so he can see the inside of a pub or a bus, stuff he wasn't allowed to come in contact with in back in the dear USA. He didn't even receive any congrats from the US president at the time, while Hitler is rumored to have shaken his hand in private. Not only is your analogy inappropriate (because you can easily implement polymorphism in C), but also flawed (because history is not as back and white as you think). Therefore, it fails as a joke too... –  Jan 29 '14 at 13:55
  • @user2341104: Thanks for the meaningful lesson, I can't believe that I have been walking around in such a state of ignorance. Oh, and I have a sense of humor for sale if you're interested. – Ed S. Jan 29 '14 at 17:57
  • @EdS. you have? You should have gone for something that is actually funny then :D –  Jan 29 '14 at 18:04

3 Answers3

13

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.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Ooh, very clever. I like that. – Robert Dec 16 '10 at 01:40
  • 5
    @Robert, I'd like to claim credit but, in the words of an immortal scientist, "the reason I have seen so far is because I've stood on the shoulders of giants". – paxdiablo Dec 16 '10 at 01:41
  • Thanks! That strategy is very cool. I'll just wait a couple of day before marking this as the answer to see if someone proposes a nicer solution. – Pedro Borges Dec 16 '10 at 01:41
  • @Pedro - It's good. The PDF I linked is a solution much like this, but it puts all the function pointers into a separate "class" struct, and has all objects point to their class. But it's much the same concept. – Chris Lutz Dec 16 '10 at 01:46
  • @Chris, that link is probably a better solution if you have lots of objects/functions, since it will save space in each object. I did something like that once but the fact that you have to double-indirect the function pointers was enough of a bother that I macro'd around it :-) Of course, the solution I put forward here allows polymorphing at the object level as well as the class level. Whether you consider that good or bad ia debatable. The ability to open an object as TCP and read it as HTML is probably dangerous, which is why I ensure the `init` functions are called for consistency. – paxdiablo Dec 16 '10 at 01:52
  • @pax - True. The OP hasn't been super clear on how extensive this is going to be. – Chris Lutz Dec 16 '10 at 01:54
  • 1
    Actually, on researching that quote to ensure it was _indeed_ Newton (see http://www.aerospaceweb.org/question/history/q0162b.shtml), I found a funny variant: "If I have not seen as far as others, it is because giants were standing on my shoulders" :-) – paxdiablo Dec 16 '10 at 03:01
1

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

Friedrich
  • 5,916
  • 25
  • 45
0

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)

Robert
  • 6,412
  • 3
  • 24
  • 26
  • Overloading != polymorphism nor is it a prerequisite. In the sense of polymorphism through an inheritance hierarchy, the different implementations of a polymorphic function have arguments of the same types, not different ones, like overloaded functions. – Nick Meyer Dec 16 '10 at 01:42
  • It's not to be avoided. The C standard guarantees the ability to cast a `struct *` to a pointer to its first element, so if you guarantee that all your "objects" contain the same root object (down the inheritance line) you can always cast them to the same root object. – Chris Lutz Dec 16 '10 at 01:44
  • However, you can create an add function which takes 3 pointers, one of which is a summing function. That add function would be completely generic (although in this case, completely redundant, since it would only call the add function which was passed into it). – Sam Dufel Dec 16 '10 at 01:45
  • 3
    Not "usually", always. C guarantees that structures with common initial subsequences of elements are compatible. – R.. GitHub STOP HELPING ICE Dec 16 '10 at 01:48
  • Gah! Silly me, of course overloading isn't polymorphism. Tired mind... @Chris, I was talking not so much about the first element, but the first `n` elements, where `n` is the number of elements in a struct that have the same type and order. As R notes, this is something that C says you can do – Robert Dec 16 '10 at 01:48
  • @R - within one compiler, unless I'm mistaken. This could break down across compilers, or over a network, but it usually doesn't. You're right though, I typed it backwards. Fixed – Robert Dec 16 '10 at 01:49
  • @Robert - But you can just wrap up the first `n` elements in one `struct` that other objects include as their first element, thereby having standard-guaranteed casting. – Chris Lutz Dec 16 '10 at 01:52
  • @Sam that solution is interesting too! – Pedro Borges Dec 16 '10 at 01:52
  • @Robert: Of course, but that's merely a trivial consequence of the fact that even an *identical* `struct` isn't necessarily compatible across compilation environments. – caf Dec 16 '10 at 05:56