I am starting a side project in C which requires multiple storage backends to be driven by a particular piece of logic. These storage backends would each be linked with the decision of which one to use specified at runtime.
So for example if I invoke my program with one set of parameters it will perform the operations in memory but if I change the program configuration it would write to disk.
The underlying idea is that each storage backend should implement the same protocol. In other words the logic for performing operations should need to know which backend it is operating on.
Currently the way I have thought to provide this indirection is to have a struct
of function pointers with the logic calling these function pointers. Essentially the struct would contain all the operations needed to implement the higher level logic E.g.
struct Context {
void (* doPartOfDoOp)(void)
int (* getResult)(void);
}
//logic.h
void doOp(Context * context) {
//bunch of stuff
context->doPartOfDoOp();
}
int getResult(Context * context) {
//bunch of stuff
return context->getResult();
}
My questions is if this way of solving the problem is one a C programmer would understand? I am a Java developer by trade but enjoy using C/++. Essentially the Context struct provides an interface like level of indirection. However I would like to know if there is a more idiomatic way of achieving this.
2
Yes, a seasoned C programmer would certainly understand such constructs. Using function pointers is just about the only way you have in C to select at runtime which of a set of functions to execute.
Ans storing function pointers in a struct gives a very clear signal that those functions belong together and is a very common way to emulate methods/member-functions in C.
One change I would advise is to pass the Context
struct also as a ‘this pointer’ to the function pointers, like this:
struct Context {
void (* doPartOfDoOp)(struct Context*)
int (* getResult)(struct Context*);
};
//logic.h
void doOp(Context * context) {
//bunch of stuff
context->doPartOfDoOp(context);
}
int getResult(Context * context) {
//bunch of stuff
return context->getResult(context);
}
In C, if you have a pointer to the first member of a struct, then you can cast that pointer to the struct type itself and use it to access the other members. This allows you to build a kind of inheritance tree and makes it possible to give your back-ends “member variables”, like this:
struct FileBackend {
struct Context interface;
FILE* file;
}
void doPartOfDoOpFileBackend(struct Context* context)
{
struct FileBackend* this = (struct FileBackend)context;
// do stuff, accessing the file through this->file.
}
0
A others have said, this is OOP in C. It is normal.
Microsoft’s COM has dual definitions for all interfaces, one for C++ and one for C. Each interface is a struct
of function pointers.
FFmpeg uses this strategy for most of the important types (encoders/decoders, muxers, IO, protocols, etc.). The have alloc(create)/init/free functions to allow for version-flexible linking.
You can certainly have a struct
with both function pointers and normal data members. It is similar to C++ layout of objects. Or go all the way and have data + vtbl pointer for objects, and a table of function pointers (effectively the virtual functions).
I’d change Context
to something more concrete and specific, such as Backend
, Storage
or similar.