As promised, here is the code for a memory manager. The purpose of this class, from which all "managed" objects must inherit, is to count references to an object, and delete it when there are no more references. Also, an object can be "de-allocated" using DeleteMemoryAllocation(), which means that "it doesn't exist", even if the memory is still physically allocated and all the contents are still there.
To check for memory allocation, code would call the CheckMemoryAllocation() member function, which throws an eNoLongerAllocated exception if the memory is "dead", at which point the caller will dereference the object using the DeleteMemoryReference() global function.
There is a question I haven't quite solved yet: where to put the check for memory allocation.
- Should it be the first statement in any managed member function? This way, it would be the responsibility of the caller to enclose the code in a try block, but sometimes you want to know if it's allocated before calling a function...
- Should the caller, before actually using the object, do a small try block of try { obj->CheckMemoryAllocation() } catch (eNoLongerAllocated) { printf("Uh-oh"); return; } ? Or should it do something else?
Both have their advantages and disadvantages... I'm still trying to find the even point between the two.
And so without further ado, here is the code.
memory.h
#ifndef __MEMORY_H_
#define __MEMORY_H_
#define MEMORY_MANAGER_DEBUG
class eNoLongerAllocated
{
public:
eNoLongerAllocated();
virtual ~eNoLongerAllocated();
const char * GetReason() const { return "Unit of memory is no longer allocated."; }
};
class cMemoryManager
{
protected:
short memReferences;
short memAllocations;
bool DeleteReference(); // decrease reference count by one, and return true if ref = 0
bool DeleteMemory(); // decrease reference/allocation count by one, return true if ref = 0
public:
cMemoryManager();
virtual ~cMemoryManager();
cMemoryManager * CreateReference(); // increase reference count by one
cMemoryManager * CreateAllocation(); // increase reference + allocation by one
void CheckMemoryAllocation();
short getMemoryReferences() { return memReferences; }
short getMemoryAllocations() { return memAllocations; }
friend void DeleteMemoryAllocation(cMemoryManager * memory);
friend void DeleteMemoryReference(cMemoryManager * memory);
};
void DeleteMemoryAllocation(cMemoryManager * memory);
void DeleteMemoryReference(cMemoryManager * memory);
#endif
memory.cpp
// necessary if you want to put in print statements,
// for debugging
#include <stdio.h>
#include "memory.h"
#include <list>
using namespace std;
list<cMemoryManager*> AllocatedMemory;
eNoLongerAllocated::eNoLongerAllocated()
{
}
eNoLongerAllocated::~eNoLongerAllocated()
{
}
cMemoryManager::cMemoryManager()
{
memReferences = 1;
memAllocations = 1;
AllocatedMemory.push_back(this);
}
cMemoryManager::~cMemoryManager()
{
AllocatedMemory.remove(this);
}
cMemoryManager * cMemoryManager::CreateReference()
{
memReferences++;
return this;
}
cMemoryManager * cMemoryManager::CreateAllocation()
{
memReferences++;
memAllocations++;
return this;
}
bool cMemoryManager::DeleteReference()
{
memReferences--;
if (memReferences <= 0)
return true; // no more references - time to delete object
return false; // do not delete object
}
bool cMemoryManager::DeleteMemory()
{
memReferences--; memAllocations--;
if (memReferences <= 0)
return true; // no more references - time to delete object
return false; // do not delete object
}
void cMemoryManager::CheckMemoryAllocation()
{
if ( memAllocations <= 0 )
throw eNoLongerAllocated();
}
////////////////////////////////////
void DeleteMemoryAllocation(cMemoryManager * memory)
{
try {
memory->CheckMemoryAllocation();
if (memory->DeleteMemory())
delete memory;
} catch (eNoLongerAllocated e) {
if (memory->DeleteReference())
delete memory;
}
}
void DeleteMemoryReference(cMemoryManager * memory)
{
if ( memory->DeleteReference() )
delete memory;
}