Last active
August 29, 2015 14:22
-
-
Save DanielGibson/d6fd06173a4706788e9b to your computer and use it in GitHub Desktop.
Simple C++ Allocator that doesn't reuse memory until it's Reset()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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