Reason Why Not to Have a Delete MACro for C++

Reason why not to have a DELETE macro for C++

Personally I prefer the following

template< class T > void SafeDelete( T*& pVal )
{
delete pVal;
pVal = NULL;
}

template< class T > void SafeDeleteArray( T*& pVal )
{
delete[] pVal;
pVal = NULL;
}

They compile down to EXACTLY the same code in the end.

There may be some odd way you can break the #define system but, personally (And this is probably going to get me groaned ;) I don't think its much of a problem.

C++ Stop Preprocessor Macro Expansion

What you're trying to do is not possible, as Michael Karcher's answer states: #define delete already makes the program ill-formed, and expanding an object-like macro (outside its own expansion) cannot be avoided.

However, for your particular use case detailed in the question, a workaround is possible. You could put your #define delete into a header file (let's call it debug_delete.hxx), like this:

#ifdef delete
# undef delete
#endif
#define delete MyCustomDelete(__FILE__, __LINE__), delete

Then, create another header file (let's call it normal_delete.hxx):

#ifdef delete
# undef delete
#endif

Note in particular that there is no mechanism in these headers to prevent multiple inclusion; in fact, we want them includable an arbitrary number of times.

Then, wrap code which must use = delete; in appropriate #include directives:

class A {
#include "normal_delete.hxx"
A() = delete;
#include "debug_delete.hxx"
~A() { delete p; }
};

(Yes, it's ugly, but what you're doing is sort of ugly in the first place, so ugly code may be required to make it work).

When should you use macros instead of inline functions?

There's a couple of strictly evil things about macros.

They're text processing, and aren't scoped. If you #define foo 1, then any subsequent use of foo as an identifier will fail. This can lead to odd compilation errors and hard-to-find runtime bugs.

They don't take arguments in the normal sense. You can write a function that will take two int values and return the maximum, because the arguments will be evaluated once and the values used thereafter. You can't write a macro to do that, because it will evaluate at least one argument twice, and fail with something like max(x++, --y).

There's also common pitfalls. It's hard to get multiple statements right in them, and they require a lot of possibly superfluous parentheses.

In your case, you need parentheses:

#define radian2degree(a) (a * 57.295779513082)

needs to be

#define radian2degree(a) ((a) * 57.295779513082)

and you're still stepping on anybody who writes a function radian2degree in some inner scope, confident that that definition will work in its own scope.

Why doesn't delete set the pointer to NULL?

Stroustrup himself answers. An excerpt:

C++ explicitly allows an
implementation of delete to zero out
an lvalue operand, and I had hoped
that implementations would do that,
but that idea doesn't seem to have
become popular with implementers.

But the main issue he raises is that delete's argument need not be an lvalue.

Is the pointer guaranteed to preserve its value after `delete` in C++?

No, it's not guaranteed and an implementation may legitimately assign zero to an lvalue operand to delete.

Bjarne Stroustrup had hoped that implementations would choose to do this, but not many do.

http://www.stroustrup.com/bs_faq2.html#delete-zero

Which version of safe_delete is better?

Clearly the function, for a simple reason. The macro evaluates its argument multiple times. This can have evil side effects. Also the function can be scoped. Nothing better than that :)

NULL check before deleting an object with an overloaded delete

No, don't check for null. The standard says that delete (T*)0; is valid. It will just complicate your code for no benefits. If operator delete is overloaded it's better to check for null in the implementation of the operator. Just saves code lines and bugs.

EDIT: This answer was accepted and upvoted, yet, in my opinion, it was not very informative. There is one missing piece in all answers here, and, for conscience sake, let me add this last piece here.

The standard actually says in [basic.stc.dynamic], at least since C++03:

Any allocation and/or deallocation functions defined in a C++ program, including the default versions in the library, shall conform to the semantics specified in 3.7.4.1 and 3.7.4.2.

Where the referenced sections, as well as some other places in the standard listed in other answers, say that the semantics of passing a null pointer are a no-op.

How to replace delete and delete []

This isn't a good idea. Macros are not the proper tool for the job. Aside from the fact that it probably isn't possible, it will be confusing to anyone maintaining your code. There are several superior alternatives.

If you have C++11, you should prefer to use smart pointers such as std::unique_ptr and std::shared_ptr whenever possible. MatthiasB also makes a valid point that you should opt to take advantage of the standard library and use std::vector instead of dynamically allocated arrays.

Otherwise, you can take advantage of tools like valgrind and LLVM's AddressSanitizer. It's available in Clang and has been ported to GCC 4.9. To use it, specify -fsanitize=address at the command line.

If you insist on textual replacement, a manual search-and-replace in your favorite IDE might do the trick. I wouldn't recommend it though.

A discussion in Is it good practice to NULL a pointer after deleting it? reveals that this practice is indicative of possible design problems in your program. For example, jalf's answer:

Setting pointers to NULL after you've deleted what it pointed to
certainly can't hurt, but it's often a bit of a band-aid over a more
fundamental problem: Why are you using a pointer in the first place? I
can see two typical reasons:

  • You simply wanted something allocated on the heap. In which case wrapping it in a RAII object would have been much safer and cleaner.
    End the RAII object's scope when you no longer need the object. That's
    how std::vector works, and it solves the problem of accidentally
    leaving pointers to deallocated memory around. There are no pointers.
  • Or perhaps you wanted some complex shared ownership semantics. The pointer returned from new might not be the same as the one that
    delete is called on. Multiple objects may have used the object
    simultaneously in the meantime. In that case, a shared pointer or
    something similar would have been preferable.

My rule of thumb is that if you leave pointers around in user code,
you're Doing It Wrong. The pointer shouldn't be there to point to
garbage in the first place. Why isn't there an object taking
responsibility for ensuring its validity? Why doesn't its scope end
when the pointed-to object does?



Related Topics



Leave a reply



Submit