/* ** $Id: objects.c 803 2008-03-01 19:29:59Z phf $ ** ** Strange way of simulating object-oriented programming ** in C. Uses the "inherit.c" approach to data fields. I ** didn't do it quite like this in lecture, but it's the ** best approach I could come up with now that I gave it ** some more thought. Note that I didn't implement "real" ** inheritance, but it's pretty close. :-) */ #include #include #include // First we define a "universal base class" for objects // just like Java's Object class. Note that we "inherit" // the method table pointer down, which is going to lead // to a number of casts later when we want to access // methods not "declared" in struct object_meth below. // The alternative would be to replicate a pointer of // appropriate type on each level of the hierarchy, but // that interferes with our approach for data fields. // The other alternative would be to define the method // table pointer as void* and always cast, but that's // not much prettier either, is it. :-) struct object_meth; struct object { struct object_meth *meth; // method table (won't be used) }; struct object_meth { int (*hash)(struct object *this); // remember Java? :-) // exercise: define a default toString() method... :-) }; // We'll never actually make instances of these, but we still // need a default implementation of the hash() method. So... int object_hash(struct object *this) { return ((int) this) ^ 0xA50FA50F; // don't ask... :-) } // We also need code to "initialize" the method table for // objects. We did this in the same function that created // instances in lecture, but we'll see why it is better to // do it in two steps below. void object_init(struct object *this) { this->meth->hash = object_hash; } // Okay, now we're ready to define a first "derived" class // that "inherits" from the Object "class" above. :-) struct shape { struct object base; // "base class" provides pointer to method table }; // Note that we replicate function pointers from base classes // on each level to avoid having to write ".base.base.base" // and so on all over the place for every message send. Also, // this allows us to adjust "parameter types" as needed, see // below... struct shape_meth { // from object class, but with adjusted parameter types int (*hash)(struct shape *this); // new methods for the shape class double (*area)(struct shape *this); double (*perimeter)(struct shape *this); }; // Since the "shape class" is "abstract" we don't need // any methods or constructor; however, we need code to // initialize the method table again, here we go. void shape_init(struct shape *this) { // let the parent initialize it's methods object_init((struct object*) this); // now we set ours ((struct shape_meth*) this->base.meth)->area = NULL; ((struct shape_meth*) this->base.meth)->perimeter = NULL; } // You're getting the hang of this, right? :-) Now we can // finally do concrete class that will actually do stuff. struct rectangle { // base class again, just for method table again struct shape base; // our "instance variables" for rectangles :-) double width; double height; }; struct rectangle_meth { // from object class, but with adjusted parameter types int (*hash)(struct rectangle *this); // from shape class, but with adjusted parameter types double (*area)(struct rectangle *this); double (*perimeter)(struct rectangle *this); // if we had methods specific to rectangle but not in // shape already, we would add them here }; // There will be instances of rectangles, so we now // need actual code to compute stuff... double rectangle_area(struct rectangle *this) { return this->width*this->height; } double rectangle_perimeter(struct rectangle *this) { return 2*this->width+2*this->height; } // Note that with a concrete class, we finally have to // provide an *actual* method table, large enough to // cover all methods we define and "inherit". struct rectangle_meth rectangle_table = { NULL, // hash NULL, // area NULL // perimeter }; void rectangle_init(struct rectangle *this) { // let the parent initialize it's methods shape_init((struct shape*) this); // now we set ours ((struct rectangle_meth*) this->base.base.meth)->area = rectangle_area; ((struct rectangle_meth*) this->base.base.meth)->perimeter = rectangle_perimeter; } // And we also need a real "constructor" for rectangles. struct rectangle *rectangle_new(int width, int height) { struct rectangle *this = malloc(sizeof(struct rectangle)); assert(this != NULL); this->base.base.meth = (struct object_meth*) &rectangle_table; rectangle_init(this); this->width = width; this->height = height; return this; } // Sorry, too tired to do the circle class as well, do it // yourself if you feel like it and send me a copy. :-) int main(void) { struct rectangle *r = rectangle_new(10,10); printf("r.hash(): %d\n", ((struct rectangle_meth*) r->base.base.meth)->hash(r)); printf("r.area(): %g\n", ((struct rectangle_meth*) r->base.base.meth)->area(r)); printf("r.perimeter(): %g\n", ((struct rectangle_meth*) r->base.base.meth)->perimeter(r)); return EXIT_SUCCESS; }