This is a fixed-size memory allocator.
The core memory block is an unsigned char pointer which gets allocated on initialization. Other than that, it has pretty basic allocation and block safety checking functionality.
Would appreciate any feedback.
template <size_t block_size, size_t blocks>
class MemoryAllocator
{
// internal memory type
using uchar = unsigned char;
// byte patterns to mark memory blocks
enum Pattern : uchar
{
UNALLOCATED = 0xAA,
ALLOCATED = 0xBB
};
// meta data
static constexpr size_t pad_bytes = 3;
static constexpr size_t hb_size = pad_bytes + block_size;
static constexpr size_t bytes_allocated = hb_size * blocks;
// internal memory block
uchar* data_;
size_t open_blocks_;
public:
/**
* @brief Construct a new Memory Allocator object.
*
*/
MemoryAllocator() : open_blocks_(blocks)
{
data_ = new uchar[bytes_allocated];
std::memset(data_, 0, bytes_allocated);
// set header blocks to free
for (size_t i = 0; i < blocks; ++i)
std::memset(data_ + (i * hb_size), Pattern::UNALLOCATED, pad_bytes);
}
/**
* @brief Destructor
*
*/
~MemoryAllocator() noexcept
{
delete [] data_;
}
/**
* @brief Allocates memory.
*
* @return void*
*/
template <typename T, typename... Args>
T* Allocate(Args&&... args) noexcept
{
if constexpr (!std::is_same<T, void>::value)
static_assert((alignof(T) + sizeof(T)) <= block_size, "Block cannot hold T.");
if (open_blocks_ == 0)
return nullptr;
for (size_t i = 0; i < blocks; ++i)
{
size_t offset = i * hb_size;
// safety check
bool valid = true;
for (size_t j = 0; j < pad_bytes; ++j)
{
if (data_[offset + j] != Pattern::UNALLOCATED)
{
valid = false;
break;
}
}
if (!valid) continue;
std::memset(data_ + offset, Pattern::ALLOCATED, pad_bytes);
--open_blocks_;
if constexpr (std::is_same<T, void>::value)
{
return (data_ + offset + pad_bytes);
}
else
{
return new(data_ + offset + pad_bytes) T(args...);
}
}
return nullptr;
}
/**
* @brief Frees memory.
*
* @param block
*/
void Free(void* block)
{
uchar* mem = reinterpret_cast<uchar*>(block) - pad_bytes;
// safety check
for (size_t i = 0; i < pad_bytes; ++i)
if (mem[i] != Pattern::ALLOCATED)
throw std::runtime_error("Freeing corrupted block!");
std::memset(mem, Pattern::UNALLOCATED, pad_bytes);
++open_blocks_;
if (open_blocks_ > blocks)
throw std::runtime_error("Too many blocks freed");
}
/**
* @brief Whether or not there is room for more allocations.
*
* @return true
* @return false
*/
bool CanAllocate() const noexcept
{
return open_blocks_ > 0;
}
// prevent copying of any kind
MemoryAllocator& operator=(MemoryAllocator& rhs) = delete;
MemoryAllocator(const MemoryAllocator& rhs) = delete;
MemoryAllocator(MemoryAllocator&& rhs) = delete;
};
/**
* @brief Works with the MemoryAllocator to allocate for classes.
*
* @tparam T
* @tparam blocks
*/
template <typename T, size_t blocks>
struct ClassAllocator : public MemoryAllocator<sizeof(T) + alignof(T), blocks>
{
template <typename... Args>
void* Allocate(Args&&... args) noexcept
{
return MemoryAllocator<sizeof(T) + alignof(T), blocks>::template Allocate<void>(std::forward<Args>(args)...);
}
};