* On Thu, Apr 14, 2011 at 1:18 PM, Patrick Rutkowski <rutski89@xxxxxxxxx> wrote: > Now, given that (presumably) I know the rules, why am I playing around > with this stuff and trying to subvert the system? Well, I enjoy doing > projects in C for various reasons, but I miss the polymorphic object > systems in languages like C++, Java, or Objective-C. When I went about > thinking on how to implement a polymorphic type system in C the first > thing I looked at was how C++ lays out the memory for it's object > types. What it seems to do is to take a class hierarchy like A->B->C, > figure out how much contiguous space in memory is necessary to hold > the data from all 3 classes, and then jams it all in there (with > alignment issues taken into account). You don't need special considerations here. `self-made polymorpism' just works. I implemented this (using source code generation an stuff) in C and it works on architectures that do crash on miss-alighment. BTW, this was a pleasure to implement :-) You need a virutal_method_table and some space for the members. When you have let's say Animal <- Cat you could do struct animal_class_s; typedef void (*destruct_t) (struct animal_class_s* self); typedef int (*breathe_t) (struct animal_class_s* self, int parameter); struct animal_vmt_s { destruct_t destruct_func; breathe_t breathe_func; /* breathe_t is function pointer */ }; static struct animal_vmt_s { animal_destruct; /* destructor impl but not de-allocator "delete"!! */ animal_breathe_; /* function impl of Animal::breathe(); */ } animal_vmt; struct animal_class_s { struct animal_vmt_s &vmt; /* animal_new sets this to animal_vmt */ int member; char member2; }; and "derive" a Cat: struct cat_class_s; typedef void (*destruct_t) (struct cat_class_s* self); typedef int (*breathe_t) (struct cat_class_s* self, int parameter); typedef char (*meow_t) (struct cat_class_s* self, char *parameter); /* is_a animal_s, so must have same order of animal_vmt_s * entries, could be done by having supervmt as first element, * but then more complex to use */ struct cat_vmt_s { destruct_t destruct_func; breathe_t breathe_func; /* breathe_t is function pointer */ meow_t meow_func; }; static struct cat_vmt_s { cat_destruct, cat_breathe_, /* function impl of Cat::breathe() if overwritten, * or animal_breathe_ if not overwritten */ cat_meow_ /* may be overwritten by PersianCat */ } cat_vmt; struct cat_class_s { struct cat_vmt_s *vmt; /* cat_new sets this to cat_vmt */ /* not a pointer, but fully included to reserve its space in * sizeof(): */ struct animal_class_s super; int member; char member2; }; then some operators: struct animal_s* animal_new() { struct animal_s *self = malloc(sizeof(struct animal_s)); self->vmt = &animal_vmt; animal_construct(self); /* can even access super class members: * self->member = self->super.member2; * PersianCat would use: * self->super.member = self->super.super.member2 * etc*/ return self; } /* please note this is static */ static void animal_construct(struct animal_s *self) { self->member = 0; self->member2 = 0; } /* please note this is static */ static void animal_destruct(struct animal_s *self) { /* ... */ } static int animal_breathe_(struct animal_class_s* self, int parameter) { printf("%s\n", "animal"); } /* ext interface, can/should be implemented as #define * self can also be a cat!*/ int breathe(struct animal_class_s* self, int parameter) { return self->vmt.breathe_func(self, parameter); } /* operator delete, accepts animals and cats (etc) */ void delete(struct animal_s *self) { self->vmt->destruct_func(); free(self); /* works also for cat!! */ } struct cat_s* cat_new() { struct cat_s *self = malloc(sizeof(struct cat_s)); self->vmt = &cat_vmt; cat_construct(self); /* our constructor */ return self; } /* please note this is static */ static void cat_construct(struct cat_s *self) { /* always call super constructor first*/ animal_construct((struct animal_s*)self); self->member = 0; self->member2 = '\0'; } /* please note this is static */ static void cat_destruct(struct cat_s *self) { /* ... */ /* finnally super destructor */ animal_destruct( (struct animal_s*) self); } static int cat_breathe(struct cat_class_s* self, int parameter) { printf("%s\n", "animal"); } You use it similarily as in C++: { struct animal_s *animal1, animal2, struct cat_s *cat; animal1 = animal_new(); cat = cat_new(); breathe(animal1); breathe((struct animal_s*)cat); animal2 = (struct animal_s*)cat; breathe(animal2); /* invokes cat_breathe_ */ /* needing casts, so compiler cannot help much */ meow((struct cat_s*)animal2); /* horrible style, but works */ /* meow((struct cat_s*)animal1); compiles, but crashes! */ delete(animal1); delete(cat); /* always same delete, but it calls cat_destruct */ } code examples simplified: you need some != NULL checks (malloc, free) and maybe some more casts. I did not compiled the code above, just writing from memory. you have Class::New() but you need only one delete. (note: in C++, you have operator new, which is an allocator calling the Constructor, and the operator delete, which is a deallocator which calls the Destructor first. It is easy to miss that new/construction are two things internally, because C++, Java etc all hide those details, but here you have to know this). You have to know which class firstly implements a method, because you must cast to this type. You must cast a lot, so be careful. Compiler cannot help here. But polymorpism works like a charm. > Looking at C++ set me out trying to implement a similar system, > though now after this conversation it occurs to me that in C > you might want to store the data for different related classes > in separately malloc()'d regions as in C++, for such cases simple have a pointer as member and malloc it in the constructor. static void persian_construct(struct persian_s *self, struct foo *data) { cat_construct((struct animal_s*)self); /* super constructor chain first */ /* no copy constructors here, so foo cannot be an object */ self->data = malloc(sizeof(struct foo)); memcpy(self->data, data, sizeof(*self->data)); } /* please note this is static */ static void persian_destruct(struct persian_s *self) { free(self->data); /* finnally super destructor (chain) */ cat_destruct( (struct cat_s*) self); } > , which you could then link > together like a typical linked list or tree. > > I'm not sure which method I'll go with, but either way I'm glad to > have learned about the basics of how and why alignment is done. So > thanks a million for that so far. I think implementing polymorpism teached me a lot. When considering this, which took me quite some time, I also get impressions how someone could implement dynamic methods: int breathe(struct animal_class_s* self, int parameter) { for(i=0; i<=sizeof(vmt_array)/sizeof(vmt_array[0]), i++) { if (strcmp("breathe", vmt_array[i].method_name) == 0) { ((breathe_t)vmt_array[i].function)(self, parameter); } } } which also implements duck typing :) also could support traits. all in C. Just a bit uncomfortable to write because of the casts and you need a source code generation tool, because otherwise the code would become very redundant, I think (remember all the class_new functions). or - maybe more simple - you could change self->vmt at runtime. Also RTTI is easy, just have a pointer in VMT as first entry pointing to some *char[] with all typenames. Next would be to think about multiple inheritance :-) Interesting stuff, I think :) oki, Steffen