tags:

views:

1016

answers:

12

Hi! Are there any good reasons (except "macros are evil", maybe) NOT to use the following macros ?

#define DELETE( ptr ) \
if (ptr != NULL)      \
{                     \
    delete ptr;       \
    ptr = NULL;       \
}

#define DELETE_TABLE( ptr ) \
if (ptr != NULL)            \
{                           \
    delete[] ptr;           \
    ptr = NULL;             \
}

Thanks.

+11  A: 

Because it is OK to delete a NULL(0) pointer. The is no need to check if the pointer actually is NULL(0) or not. If you want to set the pointer to NULL, after deleting, then you can overload the delete operator globally with out using macros.


It seems that I was wrong about the second point:

If you want to set the pointer to NULL, after deleting, then you can overload the delete operator globally

The thing is that if you overload the global new and delete, you could have something like this:

void* operator new(size_t size)
{
    void* ptr = malloc(size);

    if(ptr != 0)
    {
     return ptr;
    }

    throw std::bad_alloc("Sorry, the allocation didn't go well!");
}

void operator delete(void* p)
{
    free(p);
    p = 0;
}

Now, if you set p = 0; in the overloaded delete, you are actually setting the local one, but not the original p. Basically, we are getting a copy of the pointer in the overloaded delete.

Sorry, it was on the top of my head, I gave it a second thought now. Anyway, I would write template inline function to do the thing instead of writing EVIL MACROS :)

AraK
Good point :D I always forget about that.
Goz
I am interested to see how you can overloaded delete and set the pointer to null, can you add the example? When you overload "operator delete" you get the pointer by value and so setting it to null will not modify the pointer used in the original call. Currently you've got a '+1' because of the "no need to check for null pointer", but a '-1' for suggesting that you can overload operator delete and do the same thing.
Richard Corden
@Richard, it isn't possible to overload the delete operator to the same effect.
AProgrammer
@Richard Corden Man I was thinking, I even saw your comment after editing the post. Sorry for the mistake :)
AraK
@Arak: +1 :)
Richard Corden
I would recommend (re-)reading item 8 of Effective C++. There is a bunch of other magic surrounding the `new_handler` and zero byte handling. I vaguely remember something about overriding all forms of `operator new` and `operator delete` as a _best practice_ somewhere. This is actually what I was looking for in Effective C++...
D.Shawley
+21  A: 

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.

Goz
Indeed, much safer as a macro because of errors like DELETE(ptr++).
moala
Nice answer. I'll upvote you when you remove the condition :)
xtofl
I would rename the methods to AlmostSafeDelete and AlmostSafeDeleteArray. Because it is still not safe to delete pointers that were not allocated with new :)
Cătălin Pitiș
@Cătălin Pitiș: I would rename it DeleteAndNullify so that its function is more clear without reading documentation. And because "Safe" doesn't say why it's safe.
moala
since RAW pointers should be wrapped in a class for protection their destruction usually happens in the destructor. At this point the extra assignment to NULL becomes superfluous.
Martin York
Indeed. If you're setting a pointer to null after deleting it, you're doing it very wrong.
GMan
@moala: Actually, `DELETE(ptr++)` won't compile, because the assignment `ptr++ = NULL` within the macro is illegal -- you cannot assign to scalar rvalues.
FredOverflow
@Fred: I think that was hte point he was making ;)
Goz
+8  A: 
  • delete accept a NULL pointer without problem, so the tests are superfluous.
  • resetting the pointer to NULL is not always possible, so they can't be used systematically.
  • the security they bring is illusory: in my experience, most dangling pointer problems comes from pointers other than the one used to delete.
AProgrammer
Can you explain your statement "resetting the pointer to NULL is not always possible"?
morechilli
If the pointer is `const`, you can't reset it to `NULL`.
Dave Hinton
The pointer may not be an lvalue: DELETE(some_function())
Andrew Duffy
+2  A: 
  1. deleting a null pointer does nothing, so no need to check whether the pointer is null before deletion. Nullifying the deleted pointer might still be needed (but not in every case).

  2. Macros should be avoided as much as possible, because they are hard to debug, maintain, introduce possible side effects, they are not part of namespaces, etc.

  3. deleting a pointer that was not dynamically allocated with new will still be a problem...

Cătălin Pitiș
+2  A: 
  1. Macros are evil. Why not use inline templated functions?
  2. You can delete null ptrs.
  3. In many cases you don't need to set the ptr to null - destructors for instance.
jon hanson
For point 3: AFAICR, there are cases where not setting the ptr to NULL, even in destructors, can cause awful bugs.
moala
Surely only if a subsequent attempt is made to use the ptr in that destructor. Either way you would either attempt to dereference a null ptr or attempt to use a ptr to a deleted object, both undefined behaviour.
jon hanson
To be fair, accessing a null pointer is usually "less undefined" than using a deleted object, since in almost all cases the null (or very small) pointer access will result in a hardware exception. I do actually agree with you, though - if you're clearing deleted pointers, then you probably have problems with your resource handling that clearing deleted pointers cannot solve.
Steve Jessop
Basically `delete` shouldn't be written anywhere but in dtors, and that's where (contrary to moala's statement) setting the pointer to `NULL` achieves nothing. Even outside of dtors, in well-written code a deleted pointer often will be out of scope right after the `delete`. And even if that's not the case, setting the pointer to `NULL` might actually mask a bug where the pointer is accidently accessed. But the most important: Why need `delete` at all? I can count the time I have written `delete` in the last decade on the fingers of one hand. In fact, I haven't written it at all in years.
sbi
+1  A: 

Yes, you should never call delete directly. Use shared_ptr,scoped_ptr,unique_ptr or whatever smart pointer you have in your project.

Seems like a very strict rule. They have limitations as well.
Skurmedel
No, it is not a particularly strict rule. It is the easiest way to completely avoid memory leaks, What limitations are you thinking about?
Ban pointers from the standard? ;)
moala
No, not from the standard, but from application code. Pointer manipulations belong inside libraries only.
Raw pointers shouldn't be used for stuff that you _own_ - this is what the smart pointers are for. If you **never** use raw pointers for ownership, then you don't call `delete` either. It really does simplify your code a lot.
D.Shawley
+1 to compensate for downvotes. Of course there are exceptions to this rule, but as a general rule of thumb, it is correct. Although rather than simply using smart pointers as replacements, I'd consider the standard containers at least as important. You don't need new or delete to store data in a vector.
jalf
If you write an RIAA-container which you think needs to delete a pointer in its destructor, then you can probably instead use a scoped_ptr or scoped_array. This also has the advantage of being noncopyable, which blocks default copies of the containing object. The rule "use RIAA for resource handling" still applies when writing RIAA classes. grimner is assuming the availability of smart pointers - so clearly the exception to his advice is when writing those smart pointers if they're for some reason not available.
Steve Jessop
Well I obviously understand that too, but this should be an "almost never" since anyone very much wants to use delete in their RIAA/sentinel class, not to mention in some libraries when this is useful. Yes this is nitpicking. (disclaimer: I did not give any downvotes.)
Skurmedel
Heh, I spotted my weird typo in the "recent" page and came to fix it, but it's contagious.
Steve Jessop
+1  A: 
  1. macros are evil :p Seriously, consider using inlined template functions instead
  2. setting a pointer to NULL after deallocation tends to mask errors
  3. encourages if (ptr != NULL) checks as a flow control mechanism. Personally, I consider this a code smell along the lines of void foo(int arg) being replaced with void foo(int arg, bool doAdvancedThings=false)
  4. encourages usage of raw pointers to memory that needs to be deleted - shared_ptr and its relatives should always be used for ownership, raw pointers can be used for other access
  5. encourages looking at a pointer variable after deallocation, even worse using if (ptr != NULL) instead of if (ptr)... comparing pointers is another code smell
D.Shawley
"2. setting a pointer to NULL after deallocation tends to mask errors" Can you give an example?
moala
@moala: If you access a pointer after the value it pointed to was deleted, your app will crash. If you set it to `NULL`, your code might check for that and circumvent the crash. Still, you are trying to use a pointer that points to a deleted object.
sbi
@D.Shawley: AFAIK, `if (ptr != NULL)` is actually the only form guaranteed by the C and C++ standards, although no compiler vendor would dare break `if (ptr)`.
sbi
@sbi: FWIW, the Standard states the following "A _null pointer constant_ is an integral constant expression rvalue of integer type that evaluates to zero" [conv.ptr] and "A zero value, null pointer value, or null member pointer value is converted to `false`; and other value is converted to `true`" [conv.bool].I've also considered the `if (ptr)` vs. `if (ptr != NULL)` to be rather akin to `if (flag)` vs. `if (flag == true)` for Boolean flags. I guess it is really just a preference.
D.Shawley
@D.Shawley: So it seems I was wrong. Odd, as I seem to remember having this read quite often in the last decade. Maybe it's a myth, then. Thanks for correcting.
sbi
+15  A: 

Because it doesn't actually solve many problems.

In practice, most dangling pointer access problems come from the fact that another pointer to the same object exists elsewhere in the program and is later used to access the object that has been deleted.

Zeroing out one of an unknown number of pointer copies might help a bit, but usually this is a pointer that is either about to go out of scope, or set to point to a new object in any case.

From a design point of view, manually calling delete or delete[] should be relatively rare. Using objects by value instead of dynamically allocated objects where appropriatem using std::vector instead of dynamically allocated arrays and wrapping the ownership of objects that have to be dynamically allocated in an appropriate smart pointer (e.g. auto_ptr, scoped_ptr or shared_ptr) to manage their lifetime are all design approaches that make replacing delete and delete[] with a "safer" macro a comparatively low benefit approach.

Charles Bailey
+3  A: 

Your macro fails for several reasons:

  • It is a macro. It doesn't respect scoping rules or a number of other language features, making it easy to use incorrectly.
  • It can cause compile-errors: DELETE (getPtr()); won't compile, because you can't set the function call to null. Or if the pointer is const, your macro will also fail.
  • It achieves nothing. delete NULL is allowed by the standard.

Finally, as grimner said, you're trying to solve a problem that shouldn't exist in the first place. Why are you manually calling delete at all?` Don't you use the standard library containers? Smart pointers? Stack allocation? RAII?

As Stroustrup has said before, the only way to avoid memory leaks is to avoid having to call delete.

jalf
+2  A: 

Use boost::shared_ptr<> instead.

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htm

The MACRO here provides some of the functionality you are probably looking for.

George
A: 
  1. It doesn't give you much benefit. Deleting a null pointer is harmless, so the only benefit is setting the pointer to NULL after the delete. If a developer can remember to call your macro rather than delete, she can also remember to null out the pointer, so you're not really protecting yourself from a careless developer. The only benefits is that this happens in two lines rather than one.
  2. It's potentially confusing. delete is a standard part of the language. Your macro or templated function is not. So a new developer will need to look up that macro definition to understand what your code is doing.

In my judgement, the cost does not outweigh the benefit.

JohnMcG
+6  A: 

Because DELETE is already defined in winnt.h :

#define DELETE (0x00010000L)

moala
+1: Now there is a real reason for not using a macro - namespace pollution . I'd imagine that `DELETE` could show up elsewhere as well.
D.Shawley