h1

C++ custom memory management primer

There are many methods of implementing custom memory management schemes, but this is the most effective way to do it in C++. By overloading the new and delete operators we use so much, we can use whatever methods for obtaining and relinquishing the memory we wish. In the example, I use Doug Lea’s malloc (dlmalloc) and a simple method of aligning the memory to whatever power of two we wish via modular division.

But I also do something else that may be a little bit confusing: I hide the destructor and explicitly instruct users NOT to delete manually. Why? Because I actually use a reference counter management method vis a vis a singleton object manager class.

It works like this: each ManagedObject has a grab() and drop() method, used to count references. When the number of references reaches zero, the drop() method calls the queue_deletion() method in the ObjectManager singleton. The ObjectManager also keeps track of ManagedObject’s that are still kicking, so that they can be cleaned up at the very end if they weren’t properly dropped. This keeps memory leaks from being as bad. So the current code for updating in my engine is as follows:

void Engine::RunMainLoop(double timestep)
{
while (running)
{
if (!Update(timestep)) running = false;
if (!Render()) running = false;
ObjectManager::Singleton().CleanUp();
}
}

This is an example usage, but you can clean up at any time, and it is NOT bound to my engine in any way.

The ObjectManager deletes all things queued for deletion in CleanUp(). What I could do is simply start a cleanup thread that cleans up every second or so. For the threaded version it will lock the mutex for the queue to copy it over to a private queue and clear the public one so that it can be used again while the cleanup iterator goes through and deletes everything added previously. That’s just an example of how this could be used to greatly improve efficiency. But I warn you that you MUST add mutexes or critical sections if you want to thread the clean up method, because as it stands it is not thread safe.

And now for the grand finale: the actual code. These classes are free to use as always. In order to have your classes managed by them, you need only inherit ManagedObject and use the ManagedObject constructor like this:

class MyClass: public ManagedObject
{
public:
MyClass(): ManagedObject() {}

Without further ado, the code:

managed_object.h
#ifndef MANAGED_OBJECT_H
#define MANAGED_OBJECT_H

#include
#include
#include #include
#include
#include “dlmalloc.h”

class ManagedObject;

class ObjectManager
{
protected:
ObjectManager(const ObjectManager &) {}
ObjectManager &operator =(const ObjectManager &) {}

std::list managed_objects;
std::queue dead_objects;

friend ManagedObject;

ObjectManager();
~ObjectManager();

void add_object(ManagedObject *object);
void queue_deletion(ManagedObject *object);
public:
static ObjectManager& Singleton()
{
static ObjectManager singleton;
return singleton;
}

void CleanUp();
};

class ManagedObject
{
protected:
unsigned long object_refs;

virtual ~ManagedObject();

friend ObjectManager;
public:
void *operator new(size_t num_bytes);
void operator delete(void *ptr);// never explicitly call delete!!!

ManagedObject();

virtual void grab();
virtual void drop();
};

#endif

managed_object.cpp
#include “managed_object.h”

#define MANAGED_OBJECT_ALIGN 32

ObjectManager::ObjectManager() {}
ObjectManager::~ObjectManager()
{
std::list::iterator it;
for (it = managed_objects.begin(); it != managed_objects.end(); ++it)
{
dead_objects.push(*it);
}

CleanUp();
}

void ObjectManager::add_object(ManagedObject *object)
{
managed_objects.push_back(object);
}

void ObjectManager::queue_deletion(ManagedObject *object)
{
managed_objects.remove(object);
dead_objects.push(object);
}

void ObjectManager::CleanUp()
{
while (!dead_objects.empty())
{
delete dead_objects.front();
dead_objects.pop();
}
}

void ManagedObject::operator delete(void *ptr)
{
#ifdef MANAGED_OBJECT_ALIGN
size_t offset = ((size_t *)ptr)[-1];
void *mem = (void *)((char *)ptr – offset);

//std::cout << "freed memory aligned " // << MANAGED_OBJECT_ALIGN << " w/ offset of " << offset // << std::endl; dlfree(mem); #else dlfree(ptr); #endif } ManagedObject::~ManagedObject() {} void *ManagedObject::operator new(size_t num_bytes) { #ifdef MANAGED_OBJECT_ALIGN const size_t alloced_for_this = num_bytes + MANAGED_OBJECT_ALIGN + sizeof(size_t); void *mem = dlmalloc(alloced_for_this); char *ptr = (char *)mem + sizeof(size_t); ptr += MANAGED_OBJECT_ALIGN - (size_t)ptr % MANAGED_OBJECT_ALIGN; ((size_t *)ptr)[-1] = ptr - mem; //std::cout << "alloced " << alloced_for_this << " bytes aligned " // << MANAGED_OBJECT_ALIGN << " w/ offset of " << ((size_t *)ptr)[-1] // << std::endl; return ptr; #else return dlmalloc(num_bytes); #endif } ManagedObject::ManagedObject(): object_refs(1) { ObjectManager::Singleton().add_object(this); } void ManagedObject::grab() {++object_refs;} void ManagedObject::drop() { if (--object_refs == 0) { ObjectManager::Singleton().queue_deletion(this); } }[/source] A good example of usage would be something like this: [source language="C++"]MyClass *mc = new MyClass; // to delete mc->drop();

Remember that it is like a stack with grab/drop instead of push/pop when you do reference counting. You start with one reference always, but must drop more than once if there are multiple references added via grab(). If you nest your grabs and drops well and appropriately it is doubtful that should be a problem.

This became a sort of lesson about reference counting memory management, but I hope you took the time to observe the overloading of new and delete (which are always virtual, by the way) and how much power it gives you. It is also quite important to realize that you are NOT constrained by new and delete when using C++. This reference counter is a good example of what I’m talking about.

I hope this was helpful. If you liked the tutorial, go ahead and tell me! 🙂 Thanks.

One comment

  1. Awesomezorz!
    This will definitely come in handy. Good job 😉



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: