lp0 on fire

My personal blog

C89-OOP: Polymorphism

This chapter can be quite short because most of it already has been covered in the C89-OOP: interface inheritance article. The concept is the same; we use a VMT to store all references of virtual methods in there.

Virtual and override

There isn't much to say here for making virtual methods, as it's exactly the same as making an interface:

typedef struct Foo_vmt Foo_vmt;
typedef struct Foo Foo;

struct Foo_vmt
{
    void (*add)(Foo* self);
};

struct Foo
{
    Foo_vmt* vptr;
    int x;
};

static void Foo_add(Foo* self)
{
    ++self->x;
}

void Foo_ctor(Foo* self, int x)
{
    static Foo_vmt foo_vmt = {
        Foo_add
    };

    self->vptr = &foo_vmt;
    self->x = x;
}

Inherited classes can override the VMT members of Foo by providing their own implementation of the member. In order to access Bar's fields inside the Foo's VMT call, we need to downcast Foo to Bar first.

typedef struct Bar bar;

struct Bar
{
    Foo base;
    Foo_vmt* vptr;
    int y;
};

static void Bar_add(Foo* self)
{
    Bar* bar = (Bar*)self;
    ++bar->x;
    ++bar->y;
}

void Bar_ctor(Bar* self, int x, int y)
{
    static Foo_vmt bar_vmt = {
        Bar_add
    };

    Foo_ctor(&self->base, x);
    self->base.vptr = &bar_vmt;
    self->y = y;
}

You can call the method like this:

Bar bar;
Foo* foo;

Bar_ctor(&bar, 1, 2);
foo = (Foo*)&bar;
bar.base.vptr->add(foo);

Note the cast we need to do in order to pass the correct type.

Abstract methods

To mark a member abstract, simply set the member to 0 so it will throw a runtime error whenever someone tries to execute it.

static Foo_vmt foo_vmt = {
    0
};

Conclusion

You now know how to make a virtual method, how to override it, and how to make a method abstract. Next up will be a very simple type system to help you keep track of user-defined types on runtime.