Polymorphism in C HOWTO (was: Re: Alignment - Structures and Other Things)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



* 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


[Index of Archives]     [Linux C Programming]     [Linux Kernel]     [eCos]     [Fedora Development]     [Fedora Announce]     [Autoconf]     [The DWARVES Debugging Tools]     [Yosemite Campsites]     [Yosemite News]     [Linux GCC]

  Powered by Linux