Photon C++ Client API
5.0.7.3
|
Public Member Functions | |
virtual | ~AllocatorInterface (void) |
virtual void | setMaxAllocSize (size_t maxAllocSize)=0 |
virtual void * | alloc (size_t size)=0 |
virtual void | dealloc (void *p)=0 |
virtual void * | resize (void *p, size_t size)=0 |
Static Public Member Functions | |
static AllocatorInterface * | get (void) |
Custom Allocators to be used with Photons Memory Management need to inherit and implement this interface. The allocator that is used by Photon can be set through setAllocator().
|
virtual |
Destructor.
|
pure virtual |
This function gets called by MemoryManagement::setMaxAllocSize() and an implementation is required to behave as explained in the documentation of that function.
|
pure virtual |
This function gets called by EG_MALLOC and an implementation is required to behave as explained in the documentation of that macro.
|
pure virtual |
This function gets called by EG_FREE and an implementation is required to behave as explained in the documentation of that macro.
|
pure virtual |
This function gets called by EG_REALLOC and an implementation is required to behave as explained in the documentation of that macro.
|
static |
This function gets called by Photon exactly once in the lifetime of the application, right before the very first allocation by Photon is made. The Allocator that is returned by this function will be used for all allocations by Photon until your code sets a different allocator through setAllocator().
Calling setAllocator() right in the first line of main() is already too late to guarantee that every single allocation by Photon will use your custom allocator, because global and file-level static variables and constants (referred to here simply as 'globals') will be created before the program execution enters main(). If those globals are not POD-types, then they might allocate memory upon creation and in case of classes that are provided by one of the Photon libs, such allocations will happen through Photons memory management. Hence such allocations need to already use an allocator before the program enters main().
The way to set an allocator that is used for allocations by such globals, is to replace the default implementation of this function by your own implementation. This works in the same way like replacing the platforms default implementations of the global new and delete operators with your own implementations: Photon provides a default implementation of this function that gets used when you don't provide your own implementation, but when you do provide your own implementation, then the linker silently drops Photons weak-linked default implementation and replaces Photons call to it by a call to your implementation.
Usage example:
As you can can see, that example implementation of a primitive custom allocator makes operator new private. The reason for this is that once it had been set through setAllocator() an allocator MUST stay valid until you can guarantee that all memory that was given out by it, has been returned to it. Deleting an once used allocator prematurely, even after a different allocator has been set as the current allocator, is undefined behavior and will most likely lead to an access violation crash. A static local variable of AllocatorInterface::get() is guaranteed by the C++ standard to be constructed before that function returns and hence it is also guaranteed by the standard to get destructed after everything that uses that allocator.
If however you want to manage the lifetime of an allocator instance dynamically through new and delete, then you need to keep track if some of its memory might still be in use, before you can safely delete such an allocator. A simple approach to do this is reference counting:
Note that ReferenceCountedAllocator makes its destructor private to ensure that it only ever gets called by release(). A side-effect of this is that one can't return it in AllocatorInterface::get() (at least without leaking it). So there are usage scenarios for the approaches of both example custom allocators: use the approach of class Allocator for a custom allocator that should be returned by AllocatorInterface::get(), and use the approach of ReferenceCountedAllocator for a custom allocator that should be able to have a limited lifetime.
Furthermore note that your custom allocator must be thread-safe (which would not be the case for ReferenceCountedAllocator, if it would not protect mRefCount with a lock), as Photon might access it from multiple threads at once.
Finally if for some reason you don't want any allocations on the heap to happen while global and file level static variables are getting constructed, remember that it is completely up to you where the memory that you provide to Photon is coming from and how it's managed and that you can provide different allocators at different times. Hence the allocator that you return by AllocatorInterface::get() could look like this:
This variant simply allocates the memory on a static byte-array and does not reuse any memory that is returned to it (which is perfectly fine for memory that gets allocated in the constructor and deallocated in the destructor of a variable which has the same lifetime as the executable).
Note that you need to make sure that the array on which the memory is allocated is big enough to cover all requests that occur until you set a different allocator. As the required amount might change when changes in your code happen or when you update to a new Photon version, the assert() in alloc is important to avoid hard to track down crashes in unrelated code.
Be aware that this primitive variant that does not reuse any memory only makes sense when you set a different allocator through setAllocator() as early as possible because the longer you wait the bigger the static array will need to be to serve all requests without running out of memory.