C89-OOP: Type system
Downcasting and upcasting types can be hard because it's easy to lose track of which type what instance was. Since in most OOP languages we have a type system that helps with determinating the type at runtime, I thought it was a nice challenge to emulate this in C.
I found quite some research papers online on the topic, but I'm not smart enough to understand any of it so I wrote my own implementation without using any of the available papers for reference.
The goal is to provide a minimalistic system for identifying the type of an user-defined class instance. I tried to keep the implementation as simple as possible.
Type structure
typedef struct Type Type;
struct Type
{
int (*getId)(void);
};
void Type_setId(int* typeId)
{
static int id = 0;
if (*typeId == 0)
{
*typeId = ++id;
}
}
We make a Type
VMT which contains a pointer to the function returning the instance's type id. The code for generating the new id speaks for itself.
Base type
I've modified Foo
from C89-OOP: Class
to demonstrate the new things we need to add in:
typedef struct Foo Foo;
struct Foo
{
Type* type;
int x;
};
int Foo_getTypeId()
{
static int typeId = 0;
Type_setId(&typeId);
return typeId;
}
void Foo_ctor(Foo* self, int x)
{
static Type typeinfo = {
Foo_getTypeId
};
self->type = &typeinfo;
self->x = x;
}
Foo_getTypeId
generates a new id if it doesn't have one yet, and returns this id. In Foo_ctor
we create the static instance of Foo
's type information and assign it to all instances of Foo
.
Derived type
This is the modified Bar
class from C89-OOP: Single Inheritance
. Like with Foo
, quite a bit of code has been added.
typedef struct Bar Bar;
struct Bar
{
Foo base;
int y;
};
int Bar_getTypeId()
{
static int typeId = 0;
Type_setId(&typeId);
return typeId;
}
void Bar_ctor(Bar* self, int x, int y)
{
static Type typeinfo = {
Bar_getTypeId
};
Foo_ctor(&self->base, x);
self->base.type = &typeinfo;
self->y = y;
}
We change the Type
to match Bar
in the constructor. This way if we cast Bar
to Foo
it would still report it to be Bar
. This is the same behaviour as in C#.
Usage
The code isn't very clean, but it does illustrate how it works well:
Bar bar;
Foo* foo;
int isBarType;
Bar_ctor(&bar, 1, 2);
foo = (Foo*)&bar;
isBarType = foo->type->getId() == Bar_getTypeId();
To compare the types of two instances for equality:
Foo foo;
Bar bar;
int isBarType;
Foo_ctor(&foo, 1);
Bar_ctor(&bar, 1, 2);
isBarType = foo.type->getId() == bar.base.type->getId();
Conclusion
You now learned how to roll your own primitive type system. Even if this one isn't very complex, it is still quite a handful to write manually. It's not something you would need often, but when you need it you're thankful that you have it in an OOP language.