After taking a break and reviewing the comments on my first attempt at creating a fixed-size memory pool, this is my second attempt.
The memory pool should allocate each block correctly aligned in memory, so that when I call placement new on the returned memory, it isn't misaligned. This is my main goal with this implementation.
The pool is implemented as a free-list, with each block looking something like this in memory:
-------------------------------------------
| Next Block Index | Padding | Object Data |
-------------------------------------------
Here's my .h
#include <cstdint>
class FixedSizeMemoryPool
{
public:
FixedSizeMemoryPool(const uint32_t objectSize, const uint32_t nObjects);
~FixedSizeMemoryPool();
FixedSizeMemoryPool(const FixedSizeMemoryPool&)=delete;
FixedSizeMemoryPool&operator=(const FixedSizeMemoryPool&)=delete;
FixedSizeMemoryPool(FixedSizeMemoryPool&&)=delete;
FixedSizeMemoryPool&operator=(FixedSizeMemoryPool&&)=delete;
uint8_t* Alloc();
void Free(void* data);
const uint32_t GetCapacity() const;
const uint32_t GetFreeBlockCount() const;
private:
const uint32_t GetIndex(void* data) const;
const uint32_t CalculatePadding(const uint32_t objectSize) const;
uint32_t m_nFreeBlocks;
uint32_t m_Capacity;
uint32_t m_BlockSize;
uint32_t m_PaddingSize;
uint32_t m_NextFreeBlock;
uint8_t* m_pData;
};
And the .cpp:
FixedSizeMemoryPool::FixedSizeMemoryPool(const uint32_t objectSize,
const uint32_t nObjects)
: m_pData(nullptr), m_BlockSize(0), m_PaddingSize(0),
m_Capacity(nObjects), m_nFreeBlocks(nObjects), m_NextFreeBlock(0)
{
const uint32_t headerSize = sizeof(uint32_t);
const uint32_t totalObjectSize = headerSize + objectSize;
m_PaddingSize = CalculatePadding(totalObjectSize);
m_BlockSize = totalObjectSize + m_PaddingSize;
m_pData = new uint8_t[m_BlockSize * nObjects];
//Set up the next free block
for(unsigned int i = 0; i < nObjects; ++i)
{
uint8_t* hPtr = m_pData + (i * m_BlockSize);
*((uint32_t*)hPtr) = i + 1;
}
}
FixedSizeMemoryPool::~FixedSizeMemoryPool()
{
delete[] m_pData;
}
uint8_t *FixedSizeMemoryPool::Alloc()
{
if(m_NextFreeBlock == m_Capacity)
{
return nullptr;
}
uint8_t* ptr = m_pData + (m_NextFreeBlock * m_BlockSize);
m_NextFreeBlock = *((uint32_t*)ptr);
ptr += sizeof(uint32_t);
ptr += m_PaddingSize;
--m_nFreeBlocks;
return ptr;
}
void FixedSizeMemoryPool::Free(void* data)
{
uint8_t* pData = (uint8_t*)data - m_PaddingSize - sizeof(uint32_t);
*((uint32_t*)pData) = m_NextFreeBlock;
m_NextFreeBlock = GetIndex(data);
++m_nFreeBlocks;
}
const uint32_t FixedSizeMemoryPool::GetIndex(void *data) const
{
return ((uint8_t*)data - m_pData) / m_BlockSize;
}
const uint32_t FixedSizeMemoryPool::CalculatePadding(
const uint32_t objectSize) const
{
//Check the object size is a power of 2
if((objectSize & (objectSize - 1)) != 0)
{
//Get next highest power of 2
uint32_t paddingSize = 0;
paddingSize = objectSize - 1;
paddingSize |= m_PaddingSize >> 1;
paddingSize |= m_PaddingSize >> 2;
paddingSize |= m_PaddingSize >> 4;
paddingSize |= m_PaddingSize >> 8;
paddingSize |= m_PaddingSize >> 16;
++paddingSize ;
paddingSize = paddingSize - objectSize;
return paddingSize;
}
return 0;
}
const uint32_t FixedSizeMemoryPool::GetCapacity() const
{
return m_Capacity;
}
const uint32_t FixedSizeMemoryPool::GetFreeBlockCount() const
{
return m_nFreeBlocks;
}
uint32_t
(presumably intended to bestd::uint32_t
, though that's not a given) for the object size and count, instead ofstd::size_t
? Is there a good reason to limit objects to be less than 4GB, or to hold more than 4 billion of them? \$\endgroup\$