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?

9
  • 4
    "Polymorphism in C".. is that kind of like "Tolerance in the Nazi regime"? Commented Dec 16, 2010 at 1:35
  • 3
    planetpdf.com/codecuts/pdfs/ooc.pdf Commented Dec 16, 2010 at 1:36
  • I know, I know. It was a joke, yeesh. Maybe not a good one, but still, a joke nonetheless =D Commented Dec 16, 2010 at 1: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 Commented Dec 16, 2010 at 1:48
  • 1
    Near-duplicate of stackoverflow.com/questions/415452/object-orientation-in-c Commented Dec 16, 2010 at 1:50

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Ooh, very clever. I like that.
@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".
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 - 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.
Actually, on researching that quote to ensure it was indeed Newton (see 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" :-)
|
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

2 Comments

yeah why not mentioning COM while you're at it ?
Yes COM would be an example too.
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)

9 Comments

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.
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.
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).
Not "usually", always. C guarantees that structures with common initial subsequences of elements are compatible.
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
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.