The basic concept of a memory pool is to allocate a large portion of memory for your application, and, later on, instead of using plain new
to request memory from the O/S, you return a chunk of the previously allocated memory instead.
In order to make this work, you need to manage memory usage yourself and cannot rely on the O/S; i.e., you'll need to implement your own versions of new
and delete
, and use the original versions only when allocating, freeing, or potentially resizing your own memory pool.
The first approach would be to define one's own Class that encapsules a memory pool and provides custom methods that implement the semantics of new
and delete
, but take memory from the pre-allocated pool. Remember, this pool is nothing more than an area of memory that had been allocated using new
and has an arbitrary size. The pool's version of new
/delete
return resp. take pointers. The simplest version would probably look like C code:
void *MyPool::malloc(const size_t &size)
void MyPool::free(void *ptr)
You can pepper this with templates to automatically add conversion, e.g.
template <typename T>
T *MyClass::malloc();
template <typename T>
void MyClass::free(T *ptr);
Notice that, thanks to the template arguments, the size_t size
argument can be omitted since the compiler allows you to call sizeof(T)
in malloc()
.
Returning a simple pointer means that your pool can only grow when there's adjacent memory available, and only shrink if the pool memory at its "borders" is not taken. More specifically, you cannot relocate the pool because that would invalidate all pointers your malloc function returned.
A way to fix this limitation is to return pointers to pointers, i.e., return T**
instead of simply T*
. That allows you to change the underlying pointer while the user-facing part remains the same. Incidentially, that has been done for the NeXT O/S, where it was called a "handle". To access the handle's contents, one had to call (*handle)->method()
, or (**handle).method()
. Eventually, Maf Vosburg invented a pseudo-operator that exploited operator precedence to get rid of the (*handle)->method()
syntax: handle[0]->method();
It was called the sprong operator.
The benefits of this operation are: First, you avoid the overhead of a typical call to new
and delete
, and second, your memory pool ensures that a contiguous segment of memory is used by your application, i.e., it avoids memory fragmentation and therefore increases CPU cache hits.
So, basically, a memory pool provides you with a speedup you gain with the downside of a potentially more complex application code. But then again, there are some implementations of memory pools that are proven and can simply be used, such as boost::pool.