lp0 on fire

My personal blog

C89-DP: Prototype

Apparently the goal of this pattern is to cache costly objects and clone them instead of requesting them again. I've never used this before, so it should be a nice challenge!

IClonable

This is what all clonable classes will be implementing.

typedef struct IClonable IClonable;

struct IClonable
{
    void (*clone)(IClonable* self, IClonable* other);
};

Something clonable

We'll be re-using Foo from C89-OOP: Class but with a minor twist; it's going to have IClonable implemented.

typedef struct Foo Foo;

struct Foo
{
    IClonable* iclonable;
    int x;
};

void Foo_clone(IClonable* self, IClonable* other)
{
    Foo* a = (Foo*)self;
    Foo* b = (Foo*)other;
    b->x = a->x;
}

void Foo_ctor(Foo* self, int x)
{
    static IClonable iclonable = {
        Foo_clone
    };

    self->iclonable = &iclonable;
    self->x = x;
}

Foo_clone is where the magic happens. We just copy the values over to the other instance. We initialize the cache the first time Foo_clone is called.

Cache

Normally you would make a list or array for this, but in our case having an indexed static instance is good enough. I use an array of ints (C89 doesn't have a bool type build-in) to keep track of which values are initialized.

#define ISLOADED_LENGTH 1

enum CacheIds
{
    CACHE_FOO = 0
};

static int isLoaded[ISLOADED_LENGTH] = { 0 };

void Cache_get(IClonable* instance, int id)
{
    static Foo foo = { 0 };

    if (!isLoaded[CACHE_FOO])
    {
        Foo_ctor(&foo, 1);
        isLoaded[CACHE_FOO] = 1;
    }

    switch (id)
    {
        case CACHE_FOO:
            foo.iclonable->clone((IClonable*)&foo, instance);
            return;
    }
}

The idea shares alot with the Factory pattern discussed before in the article C89-DP: Factory. But instead of running the constructor, we are cloning the values into the instance of Foo.

Usage

As simple as it gets:

Foo foo;
int val;

Cache_get((IClonable*)&foo, CACHE_FOO);
val = foo.x;

Thoughts

While this works, I personally would instead just have a clone function like this and leave out the IClonable interface and Cache_get:

void Foo_clone(Foo* self)
{
    static int isLoaded = 0;
    static Foo cache = { 0 };

    if (!isLoaded)
    {
        Foo_ctor(&cache, 1);
        isLoaded = 1;
    }

    self->x = cache.x;
}

Afterwards just call Foo_clone directly:

Foo foo;
int val;

Foo_clone(&foo);
val = foo.x;

I think the IClonable interface starts to make more sense when you start to use the type system for automatic detection of which type needs to be initialized. Adapt the code from above with minor tweaks, and you got a more robuust system going on for less complexity.

struct IClonable
{
    void (*clone)(IClonable* self);
};

void Foo_clone(IClonable* self)
{
    static int isLoaded = 0;
    static Foo cache = { 0 };
    Foo* foo = (Foo*)self;

    if (!isLoaded)
    {
        Foo_ctor(&cache, 1);
        isLoaded = 1;
    }

    foo->x = cache.x;
}

void Cache_get(IClonable* instance)
{
    Type* type = (Type*)instance;

    if (type->getId() == Foo_getTypeId())
    {
        instance->clone(instance);
        return;
    }
}

This system of couse can't have multiple instances of Foo to clone from, but it works nicer in an environment where generics are important due to automatic detection of types.

Conclusion

It just works. It takes a bit of setup, but its nice that it was doable.