An implementation of multiple inheritence using class-based inheritence patterns and interface/virtual class based inheritence using standard ANSI C89 to use for future reference
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stddef.h> | |
/* | |
helloable.c | |
THIS SOURCE FILE IS IN THE PUBLIC DOMAIN | |
Notes on this implementation: | |
1. Base structs are pretty straightforward, but basically, they are | |
implemented as follows: the base struct must be the very first | |
element in the class. That way, the pointer can be cast to and from | |
the base and derived struct. Alternatively, the base can be accessed | |
just by calling the "base" parameter (which is what the base struct | |
should probably be named). You can cast to and from any base or derived | |
struct. Multiple base/derived inheritence was NOT attempted, and the | |
design would have to be different if it was. This kind of MI is gross! | |
2. Based on how the ToHelloable and FromHelloable macros are implemented, | |
every struct that implements it must have a pointer to the vtable, and | |
it must be named helloable. This is because the macros rely on the struct | |
having a member named "helloable". | |
3. Related to #2: If a base struct implements an interface, and the | |
derived struct does not, the derived struct must be cast to that base | |
struct, or else the macro won't work. You can also just pass the "base" | |
member which produces the same end result. Similarly, in this case, the | |
FromHelloable macro must have the base struct typename as the first | |
argument before it can be cast to the derived struct. Then the result | |
can be cast to the derived struct. This is an inconvenience. | |
4. Since each interface uses only one pointer to determine two pieces of | |
information, the macros must pass a pointer to the struct member. The | |
ToHelloable macro creates a pointer to the struct member. This is then | |
dereferenced to get the pointer to the vtable, and then the method is | |
called from there. if the "this" pointer is required, the non-dereferenced | |
pointer is passed as the first parameter. Unless just more interface | |
methods are required, you probably also need the base struct. That's where | |
the FromHelloable macro comes in handy. The 1st line of each method calls | |
FromHelloable with the struct type name that implemend it to get base | |
pointer by offsetting the helloable struct member pointer. That's why | |
ToHelloable needs to produce a pointer to the struct member. | |
*/ | |
struct Helloable{ | |
void (*SayHello)(void); | |
void (*SayGoodbye)(struct Helloable const **helloable, char *name); | |
}; | |
typedef struct Helloable const Helloable; | |
/*This needs to be a pointer to the struct member containing the pointer to a | |
vtable, not a pointer to the vtable! We need it to be a pointer to the struct | |
member because we are using it to get both the vtable and the struct*/ | |
#define ToHelloable(casting) &(casting->helloable) | |
#define FromHelloable(type, helloable) \ | |
((type*)((char*)helloable - offsetof(type, helloable))) | |
typedef struct | |
{ | |
int weight; | |
char *name; | |
int hunger_level; | |
} Being; | |
void Being_PrintStats(Being *being) | |
{ | |
printf("Name\t\t\t%s\nWeight\t\t\t%d lbs\nHunger level\t\t%d\n", being->name, | |
being->weight, being->hunger_level); | |
} | |
typedef struct | |
{ | |
Being base; | |
char *breed; | |
} Animal; | |
void Animal_PrintStats(Animal *animal) | |
{ | |
Being_PrintStats((Being *)animal); | |
printf("Breed\t\t\t%s\n", animal->breed); | |
} | |
typedef struct | |
{ | |
Animal base; | |
int barks_per_minute; | |
Helloable *helloable; | |
} Dog; | |
void Dog_PrintStats(Dog *dog) | |
{ | |
Animal_PrintStats((Animal *)dog); | |
printf("Barks per min\t\t%d\n", dog->barks_per_minute); | |
} | |
void Dog_SayHello(void) | |
{ | |
printf("Woof!\n"); | |
} | |
void Dog_SayGoodbye(Helloable **helloable, char *name) | |
{ | |
Being *self = (Being*)FromHelloable(Dog, helloable); | |
printf("Woof-woof, from %s to %s\n", self->name, name); | |
} | |
Helloable DogHelloableVtable = { Dog_SayHello, Dog_SayGoodbye }; | |
Dog *Dog_Create() | |
{ | |
Dog *dog = malloc(sizeof(Dog)); | |
dog->helloable = &(DogHelloableVtable); | |
return dog; | |
} | |
typedef struct | |
{ | |
Animal base; | |
double purr_frequency; | |
Helloable *helloable; | |
} Cat; | |
void Cat_PrintStats(Cat *cat) | |
{ | |
Animal_PrintStats((Animal *)cat); | |
printf("Purr frequency\t\t%.2f Hz\n", cat->purr_frequency); | |
} | |
void Cat_SayHello(void) | |
{ | |
printf("Meow!\n"); | |
} | |
void Cat_SayGoodbye(Helloable **helloable, char *name) | |
{ | |
Being *self = (Being*)FromHelloable(Cat, helloable); | |
printf("Meow-meow, from %s to %s\n", self->name, name); | |
} | |
Helloable CatHelloableVtable = { Cat_SayHello, Cat_SayGoodbye }; | |
Cat *Cat_Create() | |
{ | |
Cat *cat = malloc(sizeof(Cat)); | |
cat->helloable = &(CatHelloableVtable); | |
return cat; | |
} | |
typedef struct | |
{ | |
Being base; | |
char *occupation; | |
Helloable *helloable; | |
} Person; | |
void Person_PrintStats(Person *person) | |
{ | |
Being_PrintStats((Being *)person); | |
printf("Occupation\t\t%s\n", person->occupation); | |
} | |
void Person_SayHello(void) | |
{ | |
printf("Hello, world!\n"); | |
} | |
void Person_SayGoodbye(Helloable **helloable, char *name) | |
{ | |
Being *self = (Being*)FromHelloable(Person, helloable); | |
printf("Goodbye, from %s to %s\n", self->name, name); | |
} | |
Helloable PersonHelloableVtable = { Person_SayHello, Person_SayGoodbye }; | |
Person *Person_Create() | |
{ | |
Person *person = malloc(sizeof(Person)); | |
person->helloable = &(PersonHelloableVtable); | |
return person; | |
} | |
int main(void) | |
{ | |
Dog *dog = Dog_Create(); | |
Cat *cat = Cat_Create(); | |
Person *person = Person_Create(); | |
Helloable **my_helloable_array[3]; | |
int i; | |
dog->base.base.weight = 10; | |
dog->base.base.hunger_level = 5; | |
dog->base.base.name = "Libby"; | |
dog->base.breed = "lab"; | |
dog->barks_per_minute = 4; | |
cat->base.base.weight = 3; | |
cat->base.base.name = "Kitty"; | |
cat->base.base.hunger_level = 0; | |
cat->base.breed = "tabby"; | |
cat->purr_frequency = 21.98; | |
person->base.weight = 195; | |
cat->base.base.hunger_level = 1; | |
person->base.name = "Christian"; | |
person->base.hunger_level = 1; | |
person->occupation = "Programmer"; | |
my_helloable_array[0] = ToHelloable(dog); | |
my_helloable_array[1] = ToHelloable(cat); | |
my_helloable_array[2] = ToHelloable(person); | |
Dog_PrintStats(dog); | |
Cat_PrintStats(cat); | |
Person_PrintStats(person); | |
for (i = 0; i < 3; i++) | |
{ | |
Helloable **helloable = my_helloable_array[i]; | |
(*helloable)->SayHello(); | |
(*helloable)->SayGoodbye(helloable, "Jack"); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment