What is the reason for function pointers inside structs?

Why on the platform layer we have function pointers inside functions? I do see this pattern very common, I don't know if handmade hero by any chance shares a similar architecture with Quake but on Quake 3 things like Hunk_Alloc and platform spesific things are stored inside something called "refresh module". What is the reason for doing this? I assume it is so that we can assign function pointers to platfrom spesific functions but why not just toggle which platform layer is included? Like have a simple check

#ifdef PLATFORM_WINDOWS
#include "win32_platform.h"
#else
#include "linux_platform.h"
#endif

And we do not need function pointers. Also we would be doing indirect access to functions when using function pointers so if we have them called inside a loop that would also impact the performance? What benefits do we get when doing things this way?

There are multiple reason to do this way:

  1. Hot reloading. If you want to reload game dll at runtime then it is harder to have static dependency on platform code. It's possible, but it's way more flexible to specify indirect interface to platform dynamically.

  2. Some platforms may have alternative implementations of things. Like rendering - you may choose software vs opengl vs direct3d. Then platform renderer will be called dynamically based on different decisions. You could even offer users to provide such rendering backend themselves (meaning they compile the dll and your code calls it).

  3. Cleaner build. By not including platform functionality you are not polluting symbol namespace with various platform defines/includes/functions. Only what you need to declare for function pointer types. Sometimes it can give much faster builds (like avoiding including windows.h or similar).

As for performance - if you look at functionality platform provides, then you'll see that none of it is called on hot path. For example, OS memory allocation functions - you should not be calling in some inner loop inside frame. If you are, then that by itself is a problem, and having function pointer or regular function call won't matter. It should be fixed by not doing such functionality so often. Nowadays indirect branch predictor is way better than it used to be, having function pointer calls is not that big of deal anyway.

Thanks for the answer, it made it really clear but I didn't get the last part, Cleaner builds. I don't understand why pollution of symbol names would be any different when using indirect function calls to different files, as when they are compiled each turn into seperate object files to be linked into a single executable unless we are dynamically loading them on runtime as a dll. Is that the case with the platform layer? Because otherwise I don't get how the pollution of symbol names would be different, we either point to the functions on a seperate compilation unit or just include declerations to functions to be linked against later. Could you elaborate a bit more on that?


Replying to mmozeiko (#29642)

Ok, if are not doing single translation builds (unity builds) then that's not a problem.

It's only problem if you're including all your .c/.cpp files into one file to build as executable.