Skip to content

Instantly share code, notes, and snippets.

@DanielGibson
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DanielGibson/d6fd06173a4706788e9b to your computer and use it in GitHub Desktop.
Save DanielGibson/d6fd06173a4706788e9b to your computer and use it in GitHub Desktop.
Simple C++ Allocator that doesn't reuse memory until it's Reset()
/*
* A simple allocator that uses a fixed sized memory buffer and doesn't reuse
* memory until it's Reset()
* It's untested and not thread-safe (achieving that shouldn't be hard, though,
* see comment at AllocBlock())
*
*
* (C) 2015 Daniel Gibson
*
* License:
* This software is in the public domain. Where that dedication is not
* recognized, you are granted a perpetual, irrevocable license to copy
* and modify this file however you want.
* No warranty implied; use at your own risk.
*/
#ifndef _FRAME_ALLOC_H_
#define _FRAME_ALLOC_H_
#include <cstdlib> // size_t
#include <cassert>
#include <new> // placement new
#include <utility> // std::forward
class FrameAllocator
{
char* buf = nullptr;
char* top = nullptr;
size_t size=0;
struct AllocInfo
{
// this could be smaller with uint32_t or even uint16_t
size_t nextBlockOffset;
size_t numElements; // usually 1, more if it the block holds an array
// function-pointer to a function that calls the destructor
// (or destructors if the block was an array => numElements > 1)
void (*destroy)(void* obj, size_t num);
};
// TODO: for thread-safe allocations, this function (and possibly Reset())
// could be guarded by a mutex or spinlock.
// (as only they access and modify top, that should be enough)
void* AllocBlock(size_t blockSize, size_t numElems, void (*destroyer)(void*, size_t))
{
blockSize += sizeof(AllocInfo);
if(top + blockSize > buf + size)
{
// TODO: could throw exception instead
assert(0 && "No more memory in Frame Allocator!");
return nullptr;
}
AllocInfo* ai = reinterpret_cast<AllocInfo*>(top);
ai->nextBlockOffset = blockSize;
ai->numElements = numElems;
ai->destroy = destroyer;
void* ret = top + sizeof(AllocInfo);
top += blockSize;
return ret;
}
public:
static const size_t KB = 1024;
static const size_t MB = KB*1024;
static const size_t GB = MB*1024;
// call this like FrameAllocator fa(5*FrameAllocator::MB); for 5 MB internal size
FrameAllocator(size_t _size) : size(_size)
{
buf = new char[_size];
top = buf;
}
~FrameAllocator()
{
Reset();
delete[] buf;
}
void Reset()
{
char* end = top;
char* cur = buf;
while(cur < end)
{
AllocInfo* ai = reinterpret_cast<AllocInfo*>(cur);
if(ai->destroy != nullptr)
{
ai->destroy(cur+sizeof(AllocInfo), ai->numElements);
ai->destroy = nullptr;
}
cur += ai->nextBlockOffset;
}
// optionally you could memset the whole buffer to 0
top = buf;
}
void ResetSize(size_t newSize)
{
Reset();
size = newSize;
delete[] buf;
buf = new char[newSize];
top = buf;
}
// if you wanna invalidate a block before Reset()..
// but beware: this memory is not reused until after Reset()!
void Delete(void* obj)
{
AllocInfo* ai = reinterpret_cast<AllocInfo*>(static_cast<char*>(obj) - sizeof(AllocInfo));
if(ai->destroy != nullptr)
{
ai->destroy(obj, ai->numElements);
ai->destroy = nullptr; // so the destructor won't be called again
}
ai->numElements = 0;
}
// use like Foo* f = myFrameAllocator.Alloc<Foo>(1, "bla");
// with 1 and "bla" being constructor arguments for Foo
template<typename T, typename... Args>
T* Alloc(Args&&... args)
{
static void (*destroyer)(void*, size_t) = [](void* obj, size_t num) -> void {
T* t = static_cast<T*>(obj);
t->~T();
};
T* ret = static_cast<T*>(AllocBlock(sizeof(T), 1, destroyer));
if(ret != nullptr)
{
new(ret) T(std::forward<Args>(args)...);
}
return ret;
}
// allocates an array of type T containing numElements
// they will be constructed with the given args (if any)
// use like Foo* myArr = myFrameAllocator.Alloc<Foo>(42);
// to get an array of 42 Foo objects
template<typename T, typename... Args>
T* AllocArray(size_t numElements, Args&&... args)
{
static void (*destroyer)(void*, size_t) = [](void* obj, size_t num) -> void {
T* t = static_cast<T*>(obj);
for(size_t i=0; i<num; ++i)
{
t[i].~T();
}
};
T* ret = static_cast<T*>(AllocBlock(numElements*sizeof(T), numElements, destroyer));
if(ret != nullptr)
{
for(size_t i=0; i<numElements; ++i)
{
new(&ret[i]) T(std::forward<Args>(args)...);
}
}
return ret;
}
// allocates a "plain old data" object
// i.e. T shouldn't have a constructor or destructor or virtual methods
// (if it does anyway, that's your problem, they won't be called/set up!)
template<typename T>
T* AllocPOD()
{
return static_cast<T*>(AllocBlock(sizeof(T), 1, nullptr));
}
// allocates an array of "plain old data" objects
// i.e. T shouldn't have a constructor or destructor or virtual methods
// (if it does anyway, that's your problem, they won't be called/set up!)
// use like int* myArr = myFrameAllocator.Alloc<int>(500); for an array of 500 ints
template<typename T>
T* AllocPODArray(size_t numElements)
{
return static_cast<T*>(AllocBlock(numElements*sizeof(T), numElements, nullptr));
}
// will give you a chunk of uninitialized memory with given size, like malloc()
void* AllocData(size_t sizeInBytes)
{
return AllocBlock(sizeInBytes, 1, nullptr);
}
};
#endif // _FRAME_ALLOC_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment