Skip to main content
GameDev.net gamedev.net
🔒 Locked

ObjectPool class

Started by hupsilardee Nov 21, 2011 at 5:35 PM 6 replies 1.7k views
Original Post
hupsilardee
hupsilardee
Hello, I have read that stl is really slow and that object pools are the way to store particles and similar. here is the class I have written




template <typename Type>
class ObjectPool
{
// number of elements added to the pool
unsigned int numElements;

// pointer to the memory pool
Type* Pool;
// number of elements the pool has space for without reallocating
// should always be modified alongside Pool
unsigned int poolSize;

// Both <Pool> and <poolSize> are ONLY MODIFIED by the _Realloc function (and ctor)

public:
// reserves space for <count> elements
ObjectPool(unsigned int count = 1024)
{
numElements = 0;
poolSize = 0;
Pool = NULL;

_Realloc(count);
}
~ObjectPool()
{
Clear();
}

// beware; pointers to old elements may still be valid after this!
void Clear()
{
numElements = 0;
}
void Add(Type* elem)
{
if (NULL == elem)
return;

if (numElements+1 == poolSize)
_Realloc(numElements * 2);

memcpy(&Pool[numElements], elem, sizeof(Type));

numElements ++;
}

// WARNING: Invalidates all pointers to elements in the pool
void Remove(unsigned int index)
{
if (index < 0 || index >= numElements)
return;

Type* newPool = new Type[poolSize];

// for copying
int elemsBefore = index;
int elemsAfter = (numElements - index) - 1;

memcpy(newPool, Pool, elemsBefore * sizeof(Type));
memcpy(&newPool[index], &Pool[index+1], elemsAfter * sizeof(Type));

numElements--;
}

// get the number of elements added to the pool
unsigned int Size()
{
return numElements;
}
// get the space reserved for pool elements
unsigned int Space()
{
return poolSize;
}
// get the memory footprint of the pool
unsigned int MemorySize()
{
return poolSize * sizeof(Type);
}

// returns true if succeeded.
// if success, (*out) points to the <index>th element
// if failed, (*out) should not be trusted
bool Get(unsigned int index, Type** out)
{
if (index < 0 || index >= numElements || NULL == out)
{
return false;
}
// TODO: safety first

*out = &Pool[index];
return true;
}

private:
void _Realloc(unsigned int newSize, bool copy = true)
{
Type* oldPool = Pool;

if (newSize > 0)
{
Pool = new Type[newSize];
}
else
{
Pool = NULL;
}

poolSize = newSize;

if (oldPool)
{
if (Pool && copy)
memcpy(Pool, oldPool, numElements * sizeof(Type));
delete[] oldPool;
}
}
};


Is this good to go, or are there likely to be problems?
jmastro
jmastro
Have you profiled your app and determined that the C++ standard library containers are actually a performance problem? If not, you're probably just wasting your time.
Bregma
Bregma
Congratulations, you have just reinvented std::vector, but without some of its optimizations. How does your profiler tell you your versiono compares with the standard one?
Stephen M. Webb
Professional Free Software Developer
SiCrane
SiCrane
One pretty major issue is that you use memcpy() to copy your arrays, but use new [] and delete [] to manage them. Let's say your class holds string objects. One of your elements could hold a pointer to dynamic memory. When you memcpy() it, you create another string that points to the same dynamic memory, but when you delete the original array you free that memory. Now your copy is holding a pointer to garbage.

One minor issue is that you use _Realloc as an identifier, and identifiers with a leading underscore followed by a capital letter are reserved for the compiler implementation.
rip-off
rip-off
You don't cleanup everything correctly. The class doesn't obey the rule of three. Your container can potentially default construct lots of objects which aren't used. You alternate between using new [] and delete[] (which call constructors and destructors) and memcpy (which isn't safe for non-POD types). The code is a strange mixture of C and C++,

An object pool generally doesn't use dynamic allocation at all. There is a large up-front allocation, and the pool has a fixed size. #when you run out of space, you simply don't allow the allocation of new particles/bullets/whatever. Your code must handle this.
hupsilardee
hupsilardee
Yes, I did some quick comparison tests with QueryPerformanceCounter, and mine is on average faster to add elements to and delete, while the std::vector is faster on creation, and faster to remove elements from say halfway.
To access elements, I think mine is also faster, because all it does is set a pointer to a pointer, no messing about with iterators.

EDIT, Ok, from reading these replies, I think there will need to be a reworking... Perhaps I will do away with reallocating and instead have a fixed size.
And what are the alternatives to memcpy? Other than the = operator,
SiCrane
SiCrane
std::vector provides an operator[] overload and the at() member function to access elements without the need of iterators. You can copy ranges with the std::copy() function, though default constructing and copying is less efficient than either copy or move constructing the elements directly.
rip-off
rip-off

Yes, I did some quick comparison tests with QueryPerformanceCounter, and mine is on average faster to add elements to and delete, while the std::vector is faster on creation, and faster to remove elements from say halfway.
To access elements, I think mine is also faster, because all it does is set a pointer to a pointer, no messing about with iterators.
[/quote]
I suspect your test was flawed. Were you doing the tests in Debug or Release mode? std::vector<> really looks poor in Debug mode, as it contains lots of logic to ensure you don't shoot yourself in the foot. Feel free to post your tests.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.