I would like to present a simple trick I like to use to implement a more coder-friendly safe delete. In case you don’t know what a safe delete is, it usually is a simple macro designed to NULL out pointer when they are deleted, so that a second attempt to delete them doesn’t crash your game. In it’s most common form, safe delete will look like this:
#define safe_delete(x) { delete (x); (x) = NULL; }
Notice the curly braces, which are here to prevent code like
if (condition)
safe_delete(myPointer);
from generating unexpected results.
The problem with this is that the syntax of safe_delete is different that that of a regular delete, programmers have to remember and use it as a function. Usually, this results in the programmers not using the safe delete feature and all your efforts are lost. Of course, there is always the argument that code should never attempt to delete an object twice, but that is a different subject. If nothing else, it is a nice safety feature when you get ready to release your software.
The first improvement to the safe_delete macro is, as a matter of fact, to warn programmers that they are attempting to delete an object twice:
#define DELETED_PTR 0xFFEEFFEE
#define safe_delete(x) \
{ \
if ((x) == DELETED_PTR) \
{ \
OutputDebugString("Pointer \'" #x "\' is being deleted twice\n"); \
OutputDebugString(#__FILE__ " (" #__LINE__ ")\n"); \
} \
delete (x); \
(x) = DELETED_PTR; \
}
If you have a stack trace mechanism in your code base, you could replace the warning with a full-on back trace.
But still, this doesn’t make our safe_delete macro any more coder-friendly. We some way to be able to type:safe_delete myPointer;Introducing operator=(). The trick I am proposing is to use an overload of operator=(). You could technically use any operator for this, but = is the most obvious.
When you override operator=, an expression like:
a = b;
becomes
a.operator=(b);
So the idea is to create a bogus type with an operator=() override that will change the parameter passed in. That means, of course that in the case of our delete, we don’t want to pass in a copy of the pointer to delete, but a reference (or a pointer, whichever you like best) to it. That is, we’ll define safe_delete to the following:
struct _SafeDelete
{
void operator= (void** t)
{
delete *t;
*t = NULL;
}
static _SafeDelete theDeleteObj;
};
_SafeDelete _SafeDelete::theDeleteObj;
#define safe_delete _SafeDelete::_SafeDeleteObject = (void**)
And somewhere in a .cpp file
_SafeDelete _SafeDelete::_SafeDeleteObject;
Of course, if you’re like me, seeing this trailing & operator scares the crap out of you, and you can’t help but think that it’s just begging for trouble. It is true that there is something a lot more reassuring about writing:
&(myVectorOfObjects.GetAt(5).PointerToObject)
than:
&myVectorOfObjects->GetAt(5).PointerToObject
But according to operator precedence, everything should be fine. The & operator is the lowest on the list, except for assignment operators.
So now you can use safe delete just like you would delete.
safe_delete myPointer;