lp0 on fire

My personal blog

C89-OOP: Alternative type system

One thing I was dissatisfied with was the boilerplate code required to make the type system work. Since much of that code is shared, there I wondered if there was a way to simplify it, and I finally found it. Not only did I get rid of duplication, it also allowed for further simplification of the type system overall.

I would still recommend to use the other type system if you plan on expanding the capabilities of Type. This type system is better suited for the Arduino Uno and other embedded devices with a very small stack (~1.7KB available) where every single bit counts.

Type structure

This is where the most drastic change was made.

typedef struct Type Type;

struct Type
{
    int id;
};

void Type_ctor(Type* self, int* typeId)
{
    static int id = 0;

    if (*typeId == 0)
    {
        *typeId = ++id;
    }

    self->id = *typeId;
}

Type_ctor is now the combination of the old code and Type_setId It now describes how the type structure should be modified instead of just generating an id or changing addresses. This allows for more code reuse and keeping code related to the type system in the same place.

In addition, I changed the function pointer to plain data to reduce needless complexity and size.

Base type

The biggest change here is stripping code.

typedef struct Foo Foo;

struct Foo
{
    Type type;
    int x;
};

void Foo_ctor(Foo* self, int x)
{
    static int typeId = 0;

    Type_ctor(&self->type, &typeId);
    self->x = x;
}

Instead of storing the typeId and the VMT into the static register, now only typeId needs to be stored. All of Foo's typeId generation code was no longer needed and thus removed, and is instead handled by Type_ctor.

Derived type

Just like with Foo, most of what I did was stipping redundant code:

typedef struct Bar Bar;

struct Bar
{
    Foo base;
    int y;
};

void Bar_ctor(Bar* self, int x, int y)
{
    static int typeId = 0;

    Foo_ctor(&self->base, x);
    Type_ctor(&self->base.type, &typeId);
    self->y = y;
}

The old code for generating the type id was removed and the VMT as well. We override the existing type by running the constructor again.

Usage

The only downside to this approach is that you need to make an instance of the class of the type you want to compare against, opposed to just a function call.

Bar bar1;
Bar bar2;
Foo* foo;
int isBarType;

Bar_ctor(&bar1, 1, 2);
Bar_ctor(&bar2, 0, 0);

foo = (Foo*)&bar1;
isBarType = foo->type.id == bar2.base.type.id;

Conclusion

Overall a simpler system with less code and overall runtime costs, at the expense of cost for making comparisons against a single type a bit more complicated. I would still recommend the other type system in case you need to extend the type system further and/or if you have to compare against a single type often.