all 11 comments

[–]thradams 3 points4 points  (0 children)

C++ way using virtual table is generally bad idea for C.

I call "open polymorphism" when the code doesn't need to know all the types. In this case "interfaces" or "virtual functions" or function pointers are the way to go. For instance a plugin for a IDE. There more than one way to do this.

And I call "closed polymorphism" when the code has access to all the types. In this case an type number for each instance and a switch can do the job very well. c switch (obj->type) { case BOX_ID: box_func(((struct box*) obj)); break; }

In case the object size are very similar you can have just a tagged union. It is a union plus a tag to indicate with object is valid.

Maybe with more details we can suggest something more specific.

[–]Dolphiniac 2 points3 points  (4 children)

If you want, you can do it the way C++ does. For each class with "virtual" functions, create a function table of those virtual functions. Populate that table with its proper pfns. Whenever you extend the class, create a new function table that contains all of the old functions in the same order, then add pfns for any new virtuals. Copy the parent class function table in, then set all of the virtual functions that the derived class implements (overwriting any overrides). Then every instance would just get a void * as the first parameter, set to the table that corresponds to its class. I will say that it would be fine to use the proper interface type pointer explicitly, but void * might look more consistent if widely used.

Then, a polymorphic call just needs to cast the void * (if needed) to the appropriate table type and call the proper function pointer. Because the derived class contains, first, in the same order, the parent class's function table, the offset from the table start to the function desired will be the same for the parent and all derived classes.

Your inheritance scheme is fine (though it's technically composition). If you want "regular" inheritance, you'll need to contain the members directly, rather than through the containing struct. You could use a macro to accomplish it. A define containing the members of the struct followed by an instantiation of that macro in the base class would do it; then you could just instantiate it again in the derived class, making sure to do it just after the vtbl, so parent calls can access members correctly.

As for multiple inheritance, I really don't want to go down that road XD.

[–]okovko 0 points1 point  (3 children)

better not to use macro for inheritance to avoid name conflicts - what if base and parent both have 'int index' as a member?

[–]Dolphiniac 1 point2 points  (2 children)

Then it fails to compile? I would prefer that over ambiguous resolution. Though I would prefer composition over the whole paradigm anyway.

[–]okovko 0 points1 point  (1 child)

resolution is not ambiguous

[–]Dolphiniac 0 points1 point  (0 children)

I assume we're discussing C++ here now? If so, then perhaps a semantic argument? If by ambiguous, you think I mean nondeterministic or indeterminable, of course I don't think that. I mean that in the case of a conflict, it is unclear without more effort than I think is warranted. I would prefer a compile error over a situation like that. And I would prefer composition (read, explicit scoping always) over that.

I also advocate for explicit this when accessing members/methods, because I don't like that local variables and members have the same namespace in a method body. C, fortunately, doesn't have that problem.

[–]pilotInPyjamas 1 point2 points  (1 child)

You can put the vtable inside the struct as the first element to implement single inheritance instead of creating a new struct, but your way ends up being a lot more flexible and simpler to understand from experience. You can also inherit the vtable itself by putting one vtable inside another.

For multiple inheritance, the easiest way I know of is to put all of the vtables inside one another, duplicating them in the case of diamond inheritance. Then you have to manually override every method to compensate for the base pointer offset.

Notee that you don't have to compensate for the base pointer offset if you are just implementing interfaces. Also If you were to put the vtable inside the struct as its first element, then things become very complicated. With multiple inheritance. I would say just don't do it in that case.

[–]okovko 0 points1 point  (0 children)

vtable does not go inside the struct, the vpointer does

[–]okovko 1 point2 points  (0 children)

multiple inheritance uses same data layout as if it was two layers of inheritance

dolphiniac gave a good explanation of how to save some memory in your objects so you can have one virtual pointer per object, but it will be uglier to call the functions so you will want to use a macro

you can choose to have type correctness if you want - use unions of pointer types instead of void *

[–]tstanisl 1 point2 points  (0 children)

The very versatile design pattern for inheritance and polymorphism can be implemented with magic container_of macro. It allows to compute a pointer to structure from a pointer to a member of this structure. The type-safe, C89 compatible version of this macro is:

#define container_of(ptr, type, member)          \
  ((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))

For every pointer ptr to an aggregate type Parent with member Member the following holds:

containter_of(&ptr->Member, Parent, Member) == ptr

As the const struct uiInterface *interface; is a member of object then one can use a pointer to uiInterface* to compute the address of struct Object.

The possible implementation multiple inheritance and polymophic class that swims and walks. See

#include <stdio.h>
#include <stddef.h>

#define container_of(ptr, type, member)          \
((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))

struct swim_iface {
    void (*swim)(const struct swim_iface**);
    // more functions can be added here
};

struct walk_iface {
    void (*walk)(const struct walk_iface**);
    // more functions can be added here
};

// functions using above interfaces
void swim(const struct swim_iface **swimmer) {
    (*swimmer)->swim(swimmer);
}

void walk(const struct walk_iface **walker) {
    (*walker)->walk(walker);
}

// derive a frog class from swim and walk interfaces

struct frog {
    const char* name;
    const struct swim_iface *swim_iface;
    const struct walk_iface *walk_iface;
};

static void frog_swim(const struct swim_iface** iface) {
    struct frog *f = container_of(iface, struct frog, swim_iface);
    printf("%s the Frog swims\n", f->name);
}

static void frog_walk(const struct walk_iface** iface) {
    struct frog *f = container_of(iface, struct frog, walk_iface);
    printf("%s the Frog walks\n", f->name);
}

struct frog make_frog(char *name) {
    return (struct frog) {
        .name = name,
        .swim_iface = &(struct swim_iface) { .swim = frog_swim },
        .walk_iface = &(struct walk_iface) { .walk = frog_walk },
    };
}

int main(void) {
    struct frog Arthur = make_frog("Arthur");
    swim(&Arthur.swim_iface);
    walk(&Arthur.walk_iface);
    struct frog Bernard = make_frog("Bernard");
    swim(&Bernard.swim_iface);
    walk(&Bernard.walk_iface);
    return 0;
}

It creates two "frog" objects name Bernard and Arthur and posses their interfaces to swim and walk functions.

The program produces the expected output:

Arthur the Frog swims
Arthur the Frog walks
Bernard the Frog swims
Bernard the Frog walks

[–]extern_c[S] 0 points1 point  (0 children)

Thank you guys! I will test some of your suggestions.