This repository contains two custom allocators for C++17 that can speedup node-based STL containers such as std::list
, std::set
, etc. as well as custom object-graphs using std::shared_ptr
.
This is a header-only library, so just add the include
folder to your project's include-path.
The small tests can be compiled using the provided Makefile. Note, it assumes a clang compiler, so if you prefer using a different C++ compiler, just make the changes directly in the makefile.
-
PoolAllocator
: Drop-in replacement forstd::allocator
in STL node-based containers. Allocates a fixed chunk of memory at once and optionally uses a free-list to manage deallocated objects. -
SubtypeAllocator
: Similar toPoolAllocator
, but allows reusing the same memory-pool with multipleSubtypeAllocator
s. Can be used withstd::allocate_shared
. -
SubtypeAllocatorDriver
: A memory-pool that can be shared across multipleSubtypeAllocator
s. Always uses a free-list for deallocated objects. -
refc
: A custom implementation ofstd::shared_ptr
optimized for use withSubtypeAllocatorDriver
. Is faster and consumes less memory compared to astd::shared_ptr
used with a custom allocator, but has a restriction:refc
does not work with multiple inheritance, i.e. ifU
is subtype ofT
, then arefc<U>
can only be assigned torefc<T>
, ifT
is the first base class inU
's inheritance list (or recursively the first one inU
's first base-class' inheritance list). This is, becauserefc
requiresstatic_cast
not to do any pointer arithmetics. Note: Virtual inheritance is also problematic. -
RefcFactory
: A factory class that can allocate objects of a fixed set of types with a self-managedSubtypeAllocatorDriver
returning arefc
for each allocated object. -
SharedPtrFactory
: A factory class similar toRefcFactory
, but returnsstd::shared_ptr
s created withstd::allocate_shared
. It uses a special-purpose allocator wrapper similar toSubtypeAllocator
under the hood that increases the (de-)allocation performance compared tostd::allocatr_shared
with a normalSubtypeAllocator
. -
DefaultSharedPtrFactory
: A compatibility-class that can allocate objects of a fixed set of types withstd::make_shared
(and therefore usesstd::allocator
).
All provided allocators can customize the size of objects allocated at once using a template parameter. Currently, this parameter defaults to 1024 objects.
Caution: If you use an allocator that takes a pointer to SubtypeAllocatorDriver
in its constructor, make sure that the SubtypeAllocatorDriver
lives longer than all of the objects allocated through it.
Similarly, make sure that the RefcFactory
and SharedPtrFactory
objects live longer than all objects allocated with them.
This library provides a number of pool allocators. The below list gives a hint on when to use which of them:
- For speeding up standard node-based containers, use
PoolAllocator
. It will create a separate pool for each container-object which is fine in most cases. If the pool should be shared for multiple containers, useSubtypeAllocator
with a sharedSubtypeAllocatorDriver
. - For allocating objects that should be managed with a smart-pointer, ask the following questions:
- Do you need virtual inheritance? If yes, use
SharedPtrFactory
. - Do you need multiple inheritance? If no, use
RefcFactory
. - If yes, do you need upcasts to arbitrary base-classes? If no, use
RefcFactory
. - Otherwise use
SharedPtrFactory
. - Do you want to prepare your project that currently uses
std::shared_ptr
withstd::make_shared
for migration with pool-allocators? Start withDefaultSharedPtrFactory
and then switch to eitherSharedPtrFactory
orRefcFactory
.
- Do you need virtual inheritance? If yes, use
A note about std::unique_ptr
: You can use SubtypeAllocator
to allocate your objects and then wrap them in a std::unique_ptr
with a custom deleter that delegates to the SubtypeAllocator
's deallocate
function.
However, the same restrictions for multiple inheritance exist as for refc
.
#include <mem/PoolAllocator.hpp>
...
std::list<T, mem::PoolAllocator<T>> myList;
std::set<T, std::less<T>, mem::PoolAllocator<T>> mySet;
std::unordered_map<T, U, std::hash<T>, std::equal_to<T>, mem::PoolAllocator<std::pair<const T, U>>> myHashMap;
...
#include <mem/SubtypeAllocator/SubtypeAllocator.hpp>
...
mem::SubtypeAllocatorDriver<> driver;
// Reuse the same driver for allocating different types
// Note, that this takes linear time in the number of different types used with the driver in the worst case
// for both allocation and deallocation.
auto shared_T = std::allocate_shared<T>(mem::SubtypeAllocator<T>(&Driver), ...)
auto shared_U = std::allocate_shared<U>(mem::SubtypeAllocator<U>(&Driver), ...)
#include <mem/SubtypeAllocator/SubtypeFactory.hpp>
...
mem::SharedPtrFactory<1024, T, U> factory;
// Same as the example above with allocate_shared, but allocation and
// deallocation here take (armortized) constant time in the number of different
// allocated types.
// Note, that the allocation-block size must be specified explicitly here
auto shared_T = factory.create<T>(...);
auto shared_U = factory.create<U>(...);
#include <mem/SubtypeAllocator/SubtypeFactory.hpp>
...
mem::RefcFactory<1024, T, U> factory;
// Same as the example above with the SharedPtrFactory, but
// the refc<T>/refc<U> take less memory than shared_ptr<T>/shared_ptr<U>
auto shared_T = factory.create<T>(...);
auto shared_U = factory.create<U>(...);
#include <mem/SubtypeAllocator/SubtypeFactory.hpp>
...
// Expect to have one object of type T and one object of type U
// and therefore preallocate the memory for them.
// If these initial capacities are larger than the AllocBlockSize (here 1024),
// preallocating saves a significant number of actual memory allocations.
// The other way around may save unused memory from being allocated.
// Note: Support for SharedPtrFactory preallocations is under development.
mem::RefcFactory<1024, T, U> factory({1, 1});
auto shared_T = factory.create<T>(...);
auto shared_U = factory.create<U>(...);
#include <mem/SubtypeAllocator/refc.hpp>
...
// Singletons can easily be used with refc smart pointers.
// Just use the refc::singleton struct for holding static data and implicitly
// cast it to refc.
mem::refc<T> getSingleton(){
static mem::refc<T>::singleton singletonData(...);
return singletonData;
}
#include <mem/SubtypeAllocator/SubtypeFactory.hpp>
...
class A{...};
class B{...};
class C: public A, public B{...}
...
mem::RefcFactory<1024, A, B, C> factory;
mem::refc<A> shared_A = factory.create<A>(...);
mem::refc<B> shared_B = factory.create<B>(...);
mem::refc<C> shared_C = factory.create<C>(...);
shared_A = shared_C; // works well
shared_B = shared_C; // error: B is second in the inheritance-list of C, so beware assigning a refc<C> to refc<B>.
// In debug mode, it will trigger an assertion.
// To enable this conversion, use mem::SharedPtrFactory and std::shared_ptr instead