I've been reading about Memory Pools since I came across them in the book Game Programming Complete 4th Edition, and I decided to try and spin my own since I didn't quite understand how the one they presented in the book worked at the time.
One thing I'm uncertain of is defining the deleter in the shared_ptr. I don't like using delegates when they aren't needed, but if I try to pass in the address to the function pointer as the deleter then the program will not compile.
Memory chunks are stored in a singly-linked list, with the first N bytes of each entry in the list being a pointer to the next entry.
While it's nifty using pointer majicks to store the pointer in the head of each chunk, it is scary to look at when you don't understand pointer-to-pointers (I know I didn't when I first tried to roll it out!).
Any suggestions on changes? Improvements? Bugs I didn't see?
#include <memory>
namespace acorn
{
template<class Object>
//An immutable memory pool does not grow or shrink in size.
class ImmutableMemoryPool
{
typedef unsigned char byte;
//A pointer to the block of data we will be allocating.
byte* m_pBlock;
//A pointer to the current chunk
byte* m_pCurrentChunk;
//A pointer to the last chunk
byte* m_pLastChunk;
unsigned int m_NumberOfChunks;
unsigned int m_SizeOfChunk;
unsigned int m_SizeOfChunkData;
unsigned int m_SizeOfChunkHeader;
unsigned int m_SizeOfBlock;
public:
ImmutableMemoryPool(unsigned int numberOfChunks) : m_NumberOfChunks(numberOfChunks), m_SizeOfChunkData(sizeof(Object))
{
//Set the size of the header of each chunk
m_SizeOfChunkHeader = sizeof(byte*);
//Calculate the size of a whole chunk
m_SizeOfChunk = m_SizeOfChunkHeader + m_SizeOfChunkData;
//Calculate the size of the block of data
m_SizeOfBlock = m_SizeOfChunk * m_NumberOfChunks;
//Allocate a new block of memory
m_pBlock = new byte[m_SizeOfBlock];
//Initialize the chunk header data
InitializeHeaders();
//Set the current chunk pointer to point at the start of the block
m_pCurrentChunk = m_pBlock;
//Set the last chunk poitner to point at the last chunk
m_pLastChunk = m_pBlock + m_SizeOfBlock - m_SizeOfChunk;
}
~ImmutableMemoryPool()
{
delete[] m_pBlock;
}
//Places a chunk of memory back into the pool
void Dealloc(Object* pData)
{
byte* pConvertedData = (byte*)pData;
//Sure, call it on a nullptr - I don't care I'll just ignore it HA
if (pConvertedData != nullptr) {
//Make sure this pointer belongs to this pool
if (pConvertedData < (m_pBlock + m_SizeOfBlock) && pConvertedData > m_pBlock)
{
//Get the header
byte* pHeader = (byte*)pConvertedData - m_SizeOfChunkHeader;
//Set the tail chunk to now point to this returned chunk instead of a nullptr
byte** ppHeaderData = (byte**)m_pLastChunk;
ppHeaderData[0] = pHeader;
//Set the new tail as the returned chunk
m_pLastChunk = pHeader;
//Set the header data to point to a nullptr
ppHeaderData = (byte**)pHeader;
ppHeaderData[0] = nullptr;
//If the current chunk was pointing to a nullptr, point it to the new tail instead
if (m_pCurrentChunk == nullptr)
{
m_pCurrentChunk = pHeader;
}
}
else
{
std::cout << "This data isn't mine!" << std::endl;
}
}
}
//Returns a single chunk of memory
std::shared_ptr<Object> Alloc()
{
//If there are no available chunks, return a nullptr
if (m_pCurrentChunk == nullptr)
{
std::cout << "Sorry! No more chunks to be given out. Come back later, but for now have this nullptr!" << std::endl;
return nullptr;
}
//Get the pointer to the next chunk and set it to pNext
byte** ppChunkHeaderData = (byte**)m_pCurrentChunk;
byte* pNext = ppChunkHeaderData[0];
std::cout << (void*)m_pCurrentChunk << std::endl;
std::cout << (void*)pNext << std::endl;
//Get the data to return
void* rawReturnData = m_pCurrentChunk + m_SizeOfChunkHeader;
//Stick it into a shared pointer that automatically puts the object back into the pool when it is time.
std::shared_ptr<Object> returnPtr((Object*)rawReturnData, [=](Object* pRawData){
Dealloc(pRawData);
});
//Set the next chunk
m_pCurrentChunk = pNext;
return returnPtr;
}
protected:
void InitializeHeaders()
{
byte* pHeader = nullptr;
byte* pEnd = nullptr;
byte* pNext = nullptr;
byte** ppChunkHeader;
//Set the header and end of the block
pHeader = m_pBlock;
pEnd = m_pBlock + m_SizeOfBlock;
//Loop through and set the header of each chunk to point to the next chunk
while (pHeader < pEnd)
{
pNext = pHeader + m_SizeOfChunk;
ppChunkHeader = (byte**)pHeader;
ppChunkHeader[0] = (pNext < pEnd ? pNext : nullptr);
std::cout << "Header: " << (void*)pHeader << std::endl;
std::cout << "Next: " << (void*)pNext << std::endl;
pHeader = pNext;
}
std::cout << std::endl;
}
};
FixedSizePool
? \$\endgroup\$