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
.