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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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