/* The purpose of this program is to emulate object-oriented programming by simulating its essential elements from scratch, including inheritance, dynamic type information and dynamic dispatch. It also shows why dynamic memory allocation is required for advanced OOP. */ #include #include #include #define PI 3.1415927 #define CIRCLE 0 #define RECTANGLE 1 struct shape { char type_tag; int x; int y; }; typedef struct shape shape; void make_shape(shape* self, int x, int y) { self->x=x; self->y = y; } struct circle { char type_tag; int x; int y; double radius; }; typedef struct circle circle; void make_circle(circle *this, int x, int y, int r) { make_shape((shape*)this,x,y); // initializes just first portion of struct this->radius = r; // reliant on how memory is arranged. this->type_tag = CIRCLE; } struct rectangle { char type_tag; int x; int y; double width; double height; }; typedef struct rectangle rectangle; void make_rectangle(rectangle* this, int x, int y, int w, int h) { make_shape((shape*)this,x,y); this->width = w; this->height = h; this->type_tag = RECTANGLE; } // no "overloading" in C, must call functions by different names double area_c(circle* this) { return this->radius*this->radius*PI; } double circumference_c(circle* self) { return self->radius*2*PI; } double area_r(rectangle* this) { return this->width*this->height; } double circumference_r(rectangle* self) { return self->width*2 + self->height*2; } // want to create versions of area and circumference that can "DISPATCH" // to the correct version. But how does it decide? Can it make the decision // at compile time? But we can't even know how large to make it at compile // time. C doesn't have "overloading" but even if it did, it won't solve the // problem because OVERLOADING IS RESOLVED AT COMPILE TIME double area(shape* this) { if (this->type_tag==CIRCLE) return area_c((circle*)this); else return area_r((rectangle*)this); // this if-else does DYNAMIC DISPATCH; }// area polymorphic function // THIRD REALIZATION: we need DYNAMIC DISPATCH. // FINAL REALIZATION: THESE REQUIREMENTS ARE NOT JUST RESTRICTED TO C: they // are required by virtually all object oriented programming languages. // faster way to do dynamic dispatch, use a "dispatch vector" typedef double (*sfun)(shape*); sfun CIRCUMFERENCE[2] = {(sfun)circumference_c,(sfun)circumference_r}; double circumference(shape* this) { return CIRCUMFERENCE[this->type_tag](this); } /* void f(int x) {} void f(string y) {} f("abc") // resolved at compile time area(circle* this) {..} area(rectangle* this) {..} We can then call the right version given a *shape s, either area((circle*)s) or area((shape*)s), but how do we write the code to type cast when we don't know ourselves - when the TYPE of the real "object" is not decided until runtime. in order to make this decision overload is not enough. We need to have type information stored at RUNTIME. SECOND REALIZATION: we need DYNAMIC TYPE INFORMATION. */ // but distance is "naturally polymorphic" double distance(shape* this, shape *other) { int dx = this->x - other->x; int dy = this->y - other->y; return sqrt(dx*dx + dy*dy); } int main() { shape s; // allocates s on ... stack? Is that still possible?? char answer; printf("what kind of shape do you want? "); scanf("%c",&answer); if (answer=='c') make_circle((circle*)&s,3,4,5); else make_rectangle((rectangle*)&s,4,5,6,7); shape S[3]; // can I have some circles and some shapes, and // one loop that adds up the area of all of them? make_shape(&S[0],1000,2000); make_shape(&S[1],999,999); // look ma, it compiles and runs! // but what did you just do, junior? if (answer=='c') printf("radius %.2f\n", ((circle*)&s)->radius); else printf("height %.2f\n", ((rectangle*)&s)->height); // clearly doesn't work because the compiler doesn't know how much // memory to allocate for each shape in Shapes, not STATICALLY. THE // MEMORY CANNNOT BE ALLOCATED ON THE STACK. // FIRST REALIZATION: we need DYNAMIC MEMORY ALLOCATION (on heap). shape* Shapes[3]; Shapes[0] = (shape*)malloc(sizeof(circle)); make_circle((circle*)Shapes[0],4,5,6); Shapes[1] = (shape*)malloc(sizeof(rectangle)); make_rectangle((rectangle*)Shapes[1], 5,6,7,9); if (answer=='c') { Shapes[2] = (shape*)malloc(sizeof(circle)); make_circle((circle*)Shapes[0],40,50,60); } else { Shapes[2] = (shape*)malloc(sizeof(rectangle)); make_rectangle((rectangle*)Shapes[2],15,16,17,91); } // but dynamic memory allocation alone is not enough double totalarea=0; double totalcirc =0; int i; for(i=0;i<3;i++) { totalarea += area(Shapes[i]); totalcirc += circumference(Shapes[i]); } printf("total area %.2f, total circumference %.2f\n", totalarea,totalcirc); // Heap allocation requires deallocation in just the right way Shapes[1] = Shapes[2]; // memory leak for(i=0;i<3;i++) free(Shapes[i]); // must deallocate, but too late... // There are TWO serious memory errors here: // memory leak of original Shapes[1] // double free of same pointer Shapes[2], copied to Shapes[1] (not unique) return 0; }//main // disadvantages of oop in general, and disadvantages of doing it this way // in C.