Re: polymorphic structs with "methods"
- From: "Stephen Sprunk" <stephen@xxxxxxxxxx>
- Date: Sat, 29 Mar 2008 19:15:10 -0500
"Peter Michaux" <petermichaux@xxxxxxxxx> wrote in message news:78aa7392-c661-4558-a428-36e01d86b33d@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
/*
I want to have heterogeneous lists but treat all nodes the same
without checking some sort of struct "type" member and then using a
switch statement to call the appropriate function for that type. This
is in an effort to make my code more modular. Updating the switch
statement when a new "type" is added is not appealing. I've been
working on an idea (surely not original) about having polymorphic
struct "objects" that have function pointer members for "methods". My
code example is below and I would like to know where it sits in the
range from "hideous worst practice ever" to "yep people do that sort
of thing". If there are known improvements I could make I would
appreciate any comments, a link to a web page or book title.
*/
This is sometimes done, but if you need to do this extensively, think twice about whether you really, really need to use C rather than some language (like C++) that has all of these things built-in.
For an example of how ugly it can get when you try full-on OOP with C, look at the source for a GTK+ program -- or at GTK+'s source itself.
Part of programming is learning to use the right tool for the job. C makes a great hammer, but sometimes what you really need is a screwdriver.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct _Object {
void (*prettyPrint)(struct _Object *);
void (*destroy)(struct _Object *);
} Object;
/***********************************/
typedef struct _Number {
/* obj member must be first in this struct
for down casts of Number to Object */
Object obj;
double val;
} Number;
void prettyPrintNumber(Object *obj) {
Number *num = (Number *)obj;
printf("the number is: %g\n", num->val);
}
You shouldn't allow any Object to be printed as if it were a Number, only Numbers.
void prettyPrintNumber(Number *num) {
printf("the number is: %g\n", num->val);
}
This way, absent a bug-hiding cast, you'll get a compile-time error when you call this the wrong way. Technically it's broken, since you'll end up assigning it to a "method" pointer that wants an Object*, but that just means you need a cast there; all pointers-to-struct have the same representation, so it's safe in practice.
void destroyNumber(Object *obj) {
Number *num = (Number *)obj;
free(num);
}
Number *createNumber(double val) {
Number *num = (Number *)malloc(sizeof(Number));
num->obj.prettyPrint = prettyPrintNumber;
num->obj.destroy = destroyNumber;
num->val = val;
return num;
}
You've already messed up here, according to the general OO model... There is no constructor or destructor for Object, which you should be chaining to in the constructor and destructor for Number. (This gets important once you have multiple layers of inheritance. If you want multiple inheritance too, it gets even uglier.)
Object *constructObject(Object *obj) {
obj->prettyPrint = prettyPrintObject;
obj->destroy = destroyObject;
}
Object *newObject(void) {
Object *obj = malloc(sizeof (Object));
if (!obj) do_something();
return constructObject(obj);
}
void destroyObject(Object *obj) { }
void prettyPrintObject(Object *obj) { }
Now that you have those, the relevant items for Number:
Number *constructNumber(Number *num, double val) {
constructObject(&num.obj);
num->obj.prettyPrint = prettyPrintNumber; /* cast left out for clarity */
num->obj.destroy = destroyNumber; /* cast left out for clarity */
num->val = val;
return num;
}
Number *newNumber(double val) {
Number *num = malloc(sizeof *num);
if (!num) do_something();
return constructNumber(num);
}
void destroyNumber(number *num) {
/* val will destroy itself */
destroyObject(num->obj);
}
The reason I have broken out "new" and "construct" is that you can't chain to a parent constructor _that also allocates_ using the above model. I prefer to have the caller do (de)allocation, but I've kept a "new" form to make adapting your code easier. There's a different form that makes the obj member a pointer which solves that (and also solves multiple inheritance, but introduces other problems with polymorphism...).
All of the above comments apply to your String objects, so I've removed that code.
/***********************************/
int main(void)
{
printf("hello, world\n");
Object *n = (Object *)createNumber(21);
With my model:
Object *n = (Object *)newNumber(21);
You could also do this:
Number foo; constructNumber(&foo, 21);
Object *n = (Object *) foo;
/* don't need to know n is a Number to print it */
n->prettyPrint(n);
Object *s = (Object *)createString("test");
See above.
s->prettyPrint(s);
n->destroy(n);
s->destroy(s);
Since my destructors above don't actually deallocate:
n->destroy(n); free(n);
s->destroy(s); free(s);
If your Number and String are automatics, you can leave out the call to free(). Or you might separate destroy() and destruct(), with the former doing a free() after calling the latter, and the latter chaining up to its parents' destruct()s.
return 0;
}
/*
Thanks,
Peter
*/
--
Stephen Sprunk "God does not play dice." --Albert Einstein
CCIE #3723 "God is an inveterate gambler, and He throws the
K5SSS dice at every possible opportunity." --Stephen Hawking
.
- References:
- polymorphic structs with "methods"
- From: Peter Michaux
- polymorphic structs with "methods"
- Prev by Date: Re: Doubts about pointers
- Next by Date: Re: || putchar(ch == '\177' ? '?' : ch | 0100) == EOF)
- Previous by thread: Re: polymorphic structs with "methods"
- Next by thread: How do linkers work?
- Index(es):
Relevant Pages
|