
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
managed_object.cpp
#include "managed_object.h"
#define MANAGED_OBJECT_ALIGN 32
ObjectManager::ObjectManager() {}
ObjectManager::~ObjectManager()
{
std::list
A good example of usage would be something like this:
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.
Awesomezorz!
This will definitely come in handy. Good job