There are two ends of the spectrum here:
You can do the simple thing, and make the allocators globally-accessible things (free functions, global objects). In this case you rely on the discipline of the consumer to use the correct allocator for the subsystem in question, by calling the appropriate function or passing the appropriate allocation bucket enumeration, or whatever.
You can make the allocators instance objects, disabling the usual memory allocation functions (
new et cetera), and require all allocations to route through one of these allocator instances. You then pass these instances around to anything that is permitted to allocate memory.
The first option is straightforward, and probably the most common. It does require some minor discipline to avoid, say, using the rendering allocator in the physics subsystem, but this is rarely going to be a challenge. It plays well with code where you can't change the interfaces that much, because you don't have to introduce new parameters to shuttle allocators around. It's easier to bind up with macros to automatically pass
__LINE__ for diagnostics. But these are global points-of-entry into the allocation subsystem, in the end, which can hurt you. Particularly where multithreading is concerned.
The second option is interesting in that it lets you really strictly control who can allocate, and when. It's arguably slightly easier to build stack allocation (allocations in a stack, not allocations on the C++ stack) paradigms around. There are "no globals," which regardless of how you feel about them can be an improvement in the ability to write safe concurrent code. But it can also be cumbersome. The Stingray engine took this approach to the extreme of mandating allocator instances be passed even to basic containers (equivalents of
std::vector), which has knock-on implications for the default-constructibility of a lot of stuff and can be tedious to work with in practice, leading to some "temporary allocator" kludges that require more discipline to use correctly, without circumventing the rules about where and when you can allocate.
Practically speaking, you'll likely want to be closer to #1 than #2; even though the latter is interesting I think it's a little too rigid, at least when taking to the extreme where literally everything must have an allocator reference to construct.