9
\$\begingroup\$

I have a small program where I have a background thread that plays sound, so it needs to run really fast, but it also needs to do a small amount of memory management. I didn't want to use normal new and delete in a time critical area, so I threw this together, but set it up so I could reuse it elsewhere.

template <typename T, unsigned int capacity>
class MemoryPool
{
public:
    MemoryPool()
    {
        for (unsigned int i = 0; i < capacity; i++)
        {
            myUnusedMemory[i] = &myMemory[i];
        }
    }

    void* operator new(std::size_t size)
    {
        return myUnusedMemory[myUnusedIndex--];
    }

    void operator delete(void* ptr)
    {
        myUnusedMemory[++myUnusedIndex] = (T*)ptr;
    }

private:
    static int myUnusedIndex;
    static T myMemory[capacity];
    static T* myUnusedMemory[capacity];
};

template <typename T, unsigned int capacity> int MemoryPool<T, capacity>::myUnusedIndex = capacity-1;
template <typename T, unsigned int capacity> T MemoryPool<T, capacity>::myMemory[capacity];
template <typename T, unsigned int capacity> T* MemoryPool<T, capacity>::myUnusedMemory[capacity];

The intended usage is something like this:

struct Sound : public ag::util::MemoryPool<Sound, 100>
{
    // ...
};

Now when calling new Sound() I get one from my memory pool, which should be super fast. The only things that I can think of that could be improved are making operator new throw an exception when at capacity. I think I might be able to do away with typename T. The only reason it's needed is so that myMemory knows the size of its elements, but I feel like there's another solution here.

\$\endgroup\$

1 Answer 1

8
\$\begingroup\$

This currently will not work.

As every-time you create an object derived from MemoryPool you re-initialize the myUnusedMemory structure thus potentially messing up your management.

struct Game : public ag::util::MemoryPool<Game, 2>  {};


int main()
{
    Game* p1  = new Game;
    Game* p2  = new Game;
    delete p1;
    delete p2;

    // myUnusedMemory now set like this:
    // myUnusedMemory[0] = &myMemory[1];
    // myUnusedMemory[1] = &myMemory[0];

    // Now when we create the next one it resets the array:

    Game* p3  = new Game;
    // myUnusedMemory now set like this: (because the constructor reset the array)
    // myUnusedMemory[0] = &myMemory[0];
    // myUnusedMemory[1] = &myMemory[1];

    Game* p4  = new Game;  // Now p3 and p4 point at the same memory location
}

This line:

static T myMemory[capacity];

Is also not good as you are actually initializing the objects. Thus, when you are calling new your new operator is returning a pointer to an already initialized object . The constructor will then be re-called. Thus overriding the original content without the destructor being called (thus leaking any memory that should have been deleted).

I suppose you could call the destructor inside the new operator but that seems wasteful (and may be expensive) and sort of counter intuitive. So you want to use uninitialized memory for your pool.
You could do this:

static char myMemory[capacity * sizeof(T)];

But unfortunately this is not guaranteed to be correctly aligned.
If you dynamically allocate the memory then it will be fine. Use std::vector and it will be dynamically allocated and safe.

static std::vector<char>  myMemory(capacity * sizeof(T));
\$\endgroup\$
5
  • \$\begingroup\$ Oh duh, you're right wrt the constructor. Thank you much. \$\endgroup\$
    – Alex Ames
    Commented Dec 24, 2011 at 15:53
  • \$\begingroup\$ boost and c++11 can help with alignment. \$\endgroup\$ Commented Sep 27, 2013 at 12:20
  • \$\begingroup\$ @user1095108: C++03 works well on alignment. Just use a std::vector<> \$\endgroup\$ Commented Sep 27, 2013 at 15:16
  • \$\begingroup\$ @LokiAstari Not, if you don't want to allocate from the heap. \$\endgroup\$ Commented Sep 27, 2013 at 16:18
  • \$\begingroup\$ @user1095108: (1) don't use the term heap in C++ its not accurate. You mean "dynamic" memory. (2) It is a bad idea to use "automatic" memory as there is a size limit relative to the maximum size of the stack frame which is very variable and compiler/hardware dependant. \$\endgroup\$ Commented Sep 27, 2013 at 16:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.