[C++] Pointer Fun – SafeDelete

Introduction

SafeDelete and SafeArrayDelete are functions that will delete the data a pointer points to before setting it to NULL. The idea being that they leave the pointer in a safe NULL state so that you don’t try and use a pointer which points to invalid memory.

How to - With a Macro

You can implement a SafeDelete set-up using a macro, it might well look something like this:

#define SAFE_DELETE(ptr) { delete (ptr); (ptr) = NULL; }

Now that’s all well and good, and would perfectly do the job at hand, but we are C++ programmers and as such we prefer not to use macro’s unless we have to; after all, they can be tricky to debug since you can’t set breakpoints in them, they have no type safety which means you can come seriously unstuck if you pass something incorrect into one, and they can be tricky to read.

Attempt #1 – Just pass it a pointer… right?

Now, you might start off on your quest to convert that macro into a function by writing out the following code:

void safeDelete(void *ptr)
{
	delete ptr;
	ptr = NULL;
}

All looks fine, you are passing it in a pointer, deleting it and then setting the pointer to NULL… simple. So you use that in your application, the pointer is checked, it isn’t NULL so it gets deleted and set to NULL, cool. Then your code jumps out of the function and… what the hell?! The pointer passed into it is no longer NULL, but the data it points to has been deleted, the pointer is left dangling. Not a very safe SafeDelete.

So you’re now just left scratching your head, why did that happen? Simple, you passed a copy of the pointer into the function as a parameter, and you only changed that local copy.

People often have a misconception with pointers that they just somehow exist and magically point to a memory address, in actual fact, a pointer like the one shown above is an object on the stack that points to a memory address (also known as a pointee). If you know anything about that stack, that should tell you two things; firstly, the pointer object is passed into the function as a copy; secondly, that copy is destroyed once it falls out of scope.

The confusion over whether you are passing a pointer as a copy is easy to make since a pointer is essentially two things in one, this can be better explained by showing how const can work with a pointer.

void safeDelete(void *const ptr)
{
	delete ptr;
	ptr = NULL;
}

The above code should not compile, you should get an error about trying to assign to a value that is const. That is because you have made the pointer const, so you cannot change its value; this serves to clearly show that assigning NULL to the pointer is changing the pointer itself, and not the data it is pointing to.

Pro-tip Pass pointers as const where possible to avoid gotchas like the one above.

Attempt #2 – A pointer to… a pointer?

If you want to be able to modify the location that a pointer points to after leaving a function (that is, essentially modify the pointer object itself) you need to pass it into the function like you would any other object that you want to modify; by using a pointer or a reference. First let’s look at the pointer to a pointer approach.

void safeDelete(void **ptr)
{
	delete *ptr;
	*ptr = NULL;
}

Well, that does the job all right. Note that you have to de-reference the pointer to a pointer that was passed into it before performing the required operations on it; this is so you are dealing with the pointer object you passed in from your function, and not the local copy of the pointer to a pointer.

The problem with this approach is that since pointers to pointers can’t be implicitly casted to a void** you have to do the cast yourself when passing to the function, which just looks ugly (and would get annoying fast).

int *ptr = new int(10);
safeDelete((void**)&ptr);

Attempt #3 – A reference to a pointer (and some trickery)

Just like you can created pointers to pointers, you can also create a reference to a pointer. These have all the usual features of references in C++ such as never being NULL, the syntax for creating such a thing is shown below.

void safeDelete(void *&ptr)
{
	delete ptr;
	ptr = NULL;
}

However, this code has a major flaw since it won’t compile. Why? Because you cannot create a reference to void so we need some template trickery to get this working correctly. As well as being able to create template classes, you can also create template functions and we will use this to our advantage to be able to create a reference to a pointer to a generic type.

template <typename T>
void safeDelete(T *&ptr)
{
	delete ptr;
	ptr = NULL;
}

This uses the template argument as the type for the reference to a pointer, this template trickery also allows us to do away with the nasty void** casting when using the function.

The final code

And here are the finished functions for safeDelete and safeArrayDelete.

template <typename T>
void safeDelete(T *&ptr)
{
	delete ptr;
	ptr = NULL;
}

template <typename T>
void safeArrayDelete(T *&ptr)
{
	delete[] ptr;
	ptr = NULL;
}

Note

You might be wondering why I don’t do an if to check if the pointer is non-NULL before trying to delete it. Well the default delete operators in C++ automatically ignore the deletion of a NULL (zero) pointer, so you can in-fact delete 0 as many times as you want; adding the if check does nothing but add extra CPU cycles.

 
comments powered by Disqus