In OP's example, struct Alien extends struct Thing and adds new "virtual" functions (in the sense of functions dynamically dispatched via vtables), or in other words the AlienVTable extends the base ThingVTable.
struct Thing { struct Alien {
---\ /---
/--- VTable *vtable; | | VTable *vtable; ----\
| char *name; | ---> | char *name; |
| ---/ \--- |
| }; /* other Alien members*/ |
| }; |
| |
|--> struct ThingVTable { struct AlienVTable { <---|
---\ /---
void (*print)(Thing *self); | ---> | void (*print)(Thing *self);
---/ \---
void (*function)(Alien *self);
/* other virtual Alien functions */
}; };
The following is one way to implement this (comments refer to the rough C++ equivalent of some of the constructs, though the C code does not exactly duplicate the C++ semantics).
#ifdef _MSC_VER
#define _CRT_NONSTDC_NO_DEPRECATE // msvc strdup c4996
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct Thing { // struct Thing {
const struct ThingVTable *vtable; //
char *name; // char *name;
} Thing; //
//
typedef struct ThingVTable { //
const void *base_vtable; //
void (*print_name)(Thing *self); // virtual void print_name();
void (*print_age)(Thing *self); // virtual void print_age() = 0;
} ThingVTable; // };
void print_thing_name(Thing *self) // void Thing::print_name()
{ printf("Thing name: %s\n", self->name); } // { ... }
static const ThingVTable thing_vtable = {
.base_vtable = NULL,
.print_name = print_thing_name,
.print_age = NULL
};
void construct_thing(Thing *self, const char *name) // Thing::Thing(const char *name)
{ // : name(name) { ... }
self->vtable = &thing_vtable; //
self->name = strdup(name); //
} //
//
void destruct_thing(Thing *self) // Thing::~Thing()
{ // { ... }
free(self->name);
self->vtable = NULL;
}
Thing *new_thing(const char *name) // Thing *p = new Thing(name);
{ //
Thing *self = malloc(sizeof(Thing)); //
if (self == NULL) return NULL; //
//
construct_thing(self, name); //
return self; //
} //
//
void delete_thing(Thing *self) // delete p;
{
destruct_thing(self);
free(self);
}
typedef struct Alien { // struct Alien : Thing {
Thing super; //
int age; // int age;
} Alien; //
//
typedef struct AlienVTable { // void print_name() override;
ThingVTable super; // void print_age() override;
int (*is_et)(struct Alien *); // virtual int is_et();
// };
} AlienVTable;
// override of base virtual function
void print_alien_name(Thing *self) // void print_name()
{ printf("Alien name: %s\n", self->name); } // { ... }
//
// implementation of base pure virtual function
void print_alien_age(Thing *self) // void print_age()
{ printf("Alien age: %d\n", ((Alien *)self)->age); } // { ... }
//
// new virtual function
int is_alien_et(Alien *self) // int is_alien()
{ return 0; } // { ... }
static const AlienVTable alien_vtable = {
.super.base_vtable = &thing_vtable,
.super.print_name = print_alien_name,
.super.print_age = print_alien_age,
.is_et = is_alien_et
};
void construct_alien(Alien *self, const char *name, int age)
{
construct_thing(&self->super, name); // Alien::Alien(const char *name, int age)
self->super.vtable = (ThingVTable *)&alien_vtable; // : Thing(name),
self->age = age; // age(age)
} //
//
void destruct_alien(Alien *self) // Alien::~Alien()
{ // { ... }
self->super.vtable = &thing_vtable;
destruct_thing(&self->super);
}
Alien *new_alien(const char *name, int age) // Alien *q = new Alien(name, age);
{ //
Alien *self = malloc(sizeof(Alien)); //
if (self == NULL) return NULL; //
//
construct_alien(self, name, age); //
return self; //
} //
//
void delete_alien(Alien *self) // delete q;
{
destruct_alien(self);
free(self);
}
int main(void) {
Thing thing; // not allowed in C++ since Thing is an abstract class
construct_thing(&thing, "stack thing"); // Thing thing("stack thing");
thing.vtable->print_name(&thing); // thing.print_name();
// error: pure virtual call
// thing.vtable->print_age(&thing); // thing.print_age();
destruct_thing(&thing); /* destructor implicitly called at end of main */
printf("\n");
Alien *alien = new_alien("heap alien", 1234); // Alien *alien = new Alien("heap alien", 1234)
((ThingVTable *)((AlienVTable *)alien->super.vtable)->super.base_vtable)->print_name((Thing *)alien); // alien->Thing::print_name();
((AlienVTable *)alien->super.vtable)->super.print_name((Thing *)alien); // alien->print_name();
((AlienVTable *)alien->super.vtable)->super.print_age((Thing *)alien); // alien->print_age();
printf("Is Alien ET? %d\n", ((AlienVTable *)alien->super.vtable)->is_et(alien)); // alien->is_et();
delete_alien(alien); // delete alien;
printf("\n");
Thing *poly = (Thing *)new_alien("pointer to alien", 9786); // Thing *poly = new Alien("pointer to alien", 9786)
poly->vtable->print_name(poly); // poly->print_name();
poly->vtable->print_age(poly); // poly->print_age();
printf("Is Alien ET? %d\n", ((AlienVTable *)((Alien *)poly)->super.vtable)->is_et((Alien *)poly)); // poly->is_et();
delete_alien((Alien *)poly); // delete poly;
return 0;
}
Output:
Thing name: stack thing
Thing name: heap alien
Alien name: heap alien
Alien age: 1234
Is Alien ET? 0
Alien name: pointer to alien
Alien age: 9786
Is Alien ET? 0
Notes:
both Alien and AlienVTable mimic "inheritance" by embedding an instance of the base class as the first member (more about that at Struct Inheritance in C for example), which also legitimizes casts from pointers to a derived type to pointers to the base type;
the code could be made friendlier and easier to write/follow by using helper macros and inlines (or language extensions such as anonymous struct fields), but the intention here was to leave the internal mechanics fully exposed;
the destruct_ helpers would be prime candidates to make virtual and include in the vtable.