4 ms·
Composition? typedef struct { /* ... */ } context; typedef struct { context ctx; /* ... */ } foo_context; typedef struct { co
by mintplant 4y ago
Composition?
typedef struct { /* ... */ } context;
typedef struct {
context ctx;
/* ... */
} foo_context;
typedef struct {
context ctx;
/* ... */
} bar_context;
void some_general_method(context* ctx, int a, int b);
void some_foo_specific_method(foo_context* foo_ctx, int c, int d);
foo_context* foo_ctx;
some_foo_specific_method(foo_ctx, 0, 0);
some_general_method(&foo_ctx->ctx, 1, 1);
For checked downcasts, you could include an enum type tag inside `context` and have something like `foo_context* as_foo_context(context* ctx)` for downcasts, which, if `context` is at the beginning of `foo_context`, could just check the tag and cast the `ctx` pointer to `foo_context*` (or do a little pointer arithmetic if `context` is somewhere else in the containing struct). Return `NULL` (or assert) if the tag doesn't match.
- dromtrund 4y ago+1, this approach will also highlight cases where you're trying to generalize something that isn't as generic as you thought. I'd argue that the need for downcasting in a method working on the inner context would also be a code smell, and that you might want to reconsider the context split. In some cases, there's no way around it (like async events), but it might be more appropriate to pass additional context or a callback instead, to avoid a circular dependency.
- uecker 4y agoI use this technique a lot and it is very powerful.
- zcbenz 4y agoAdding fields to context will break ABI compatibility. To work around the problem, some libraries add padding fields, and some require passing the size of struct to determine ABI version.
- cozzyd 4y agoI'm guessing context here is a pointer or a handle?
- icedchai 4y agoI first saw this technique used in the late 80's, with the Amiga operating system. Most of the APIs were built on this sort of pattern.