18

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;             \
}
Sam
  • 7,252
  • 16
  • 46
  • 65
moala
  • 5,094
  • 9
  • 45
  • 66
  • 9
    Macros are not evil just because they are evil. They are not part of namespaces and that makes them evil. – Kirill V. Lyadvinsky Aug 12 '09 at 12:28
  • 20
    The standard states the deleting a null pointer is safe, so the check is useless. – jkeys Aug 13 '09 at 02:26
  • It's been a while since I've programmed in c++ but I had thought it was advisable to not use NULL as stated in Stroustrup's book. – Gavin Chin Aug 13 '09 at 03:40
  • Well in the upcomming c++1x standard they will FINALLY provide a null_ptr construct that isn't convertible to a number – KitsuneYMG Aug 13 '09 at 10:31
  • @Gavin Chin: related: http://stackoverflow.com/questions/704466/why-doesnt-delete-set-the-pointer-to-null – moala Aug 13 '09 at 10:46
  • @moala: I was referring to the use of a NULL macro to = 0 rather than why a pointer has to be set to 0. This should be habitual as part of any c++ programmer's daily routine. Stroustrup's book recommends not using a macro. However it's only a preference thing, I believe. – Gavin Chin Aug 16 '09 at 08:51

12 Answers12

43

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
  • 61,365
  • 24
  • 124
  • 204
  • 8
    Indeed, much safer as a macro because of errors like DELETE(ptr++). – moala Aug 12 '09 at 12:14
  • 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ș Aug 12 '09 at 12:52
  • 11
    @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 Aug 12 '09 at 12:58
  • 11
    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 Aug 12 '09 at 14:34
  • Indeed. If you're setting a pointer to null after deleting it, you're doing it very wrong. – GManNickG Aug 13 '09 at 03:39
  • @moala: Actually, `DELETE(ptr++)` won't compile, because the assignment `ptr++ = NULL` within the macro is illegal -- you cannot assign to scalar rvalues. – fredoverflow Jul 02 '10 at 10:11
  • If you wanted support for user-types with ptr semantics, could you not remove the '*' from both? – Philip Guin Oct 11 '13 at 19:22
  • @Goz I have two noob questions. Why do we need "*&"? It seems to work without them? Won't it be better if the function template is inline? – Zingam Jul 16 '14 at 10:07
  • 1
    @Zingam: If I don't pass a reference to a pointer then it WILL delete the pointer but the value of the pointer will still be the same as that passed in. By passing a reference to the pointer then any changes I make to the pointer (ie the setting to `nullptr`) won't occur. As for inline. Templates are automatically and necessarily inlined, IIRC, unless (partially) specialized. – Goz Jul 16 '14 at 10:46
  • @Goz: Thanks for the reply! I get the idea. I think I get it. Maybe it's my English. I think that the second sentence contradicts the first one. Do you mean to say: "By not passing a reference..."? – Zingam Jul 16 '14 at 14:20
  • Instead of (T*& pVal) as argument, what if I just use (T& val)? Anything wrong with that? – Santosh Tiwari Apr 19 '16 at 14:20
  • It's not exactly the same as a macro. In some cases a macro can access a private destructor (say in a static 'cleanup' function on a singleton class), where an inline template function can't. – Andy Krouwel Feb 10 '17 at 10:30
  • eg. `class Thing { public: static Thing& instance(); static Thing& cleanup() { DELETE(s_instance); } private: // Private structors to enforce singletonininity Thing(); ~Thing(); }` Dagnabbit, formatting. – Andy Krouwel Feb 10 '17 at 10:36
  • @AndyKrouwel: ... I think you might be beginning to clutch at straws slightly here ;) But yeah ... sure .. you found a weakness! – Goz Feb 10 '17 at 18:23
  • That said ... can't remember the last time I actually wrote new and delete ... I've been using smart pointers and RAII for such a long time now ... – Goz Feb 10 '17 at 18:24
  • 1
    @Goz - I wasn't deliberately trying to be obscure, honest. I just replaced the macros in my codebase with templates and it stopped compiling. – Andy Krouwel Feb 12 '17 at 15:53
  • @AndyKrouwel: Not sure I've ever actually used a private destructor before :) – Goz Feb 14 '17 at 12:35
31

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.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
16

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 :)

Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
  • 3
    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 Aug 12 '09 at 12:27
  • @Richard, it isn't possible to overload the delete operator to the same effect. – AProgrammer Aug 12 '09 at 12:33
  • @Richard Corden Man I was thinking, I even saw your comment after editing the post. Sorry for the mistake :) – Khaled Alshaya Aug 12 '09 at 12:35
  • 1
    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 Aug 13 '09 at 11:54
15

Because DELETE is already defined in winnt.h :

#define DELETE (0x00010000L)

moala
  • 5,094
  • 9
  • 45
  • 66
  • 5
    +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 Aug 13 '09 at 11:56
9
  • 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
  • 51,233
  • 8
  • 91
  • 143
5

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
  • 243,077
  • 51
  • 345
  • 550
3
  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ș
  • 14,123
  • 2
  • 39
  • 62
3
  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
  • 8,722
  • 2
  • 37
  • 61
  • For point 3: AFAICR, there are cases where not setting the ptr to NULL, even in destructors, can cause awful bugs. – moala Aug 12 '09 at 12:12
  • 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 Aug 12 '09 at 12:39
  • 1
    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 Aug 12 '09 at 13:08
  • 1
    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 Aug 12 '09 at 14:04
3
  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
Community
  • 1
  • 1
D.Shawley
  • 58,213
  • 10
  • 98
  • 113
  • "2. setting a pointer to NULL after deallocation tends to mask errors" Can you give an example? – moala Aug 12 '09 at 13:00
  • @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 Aug 12 '09 at 14:07
  • @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 Aug 12 '09 at 14:08
  • 2
    @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 Aug 12 '09 at 14:54
  • @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 Aug 13 '09 at 10:18
3

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 Godik
  • 1,716
  • 1
  • 14
  • 19
2

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

grimner
  • 776
  • 5
  • 9
  • Seems like a very strict rule. They have limitations as well. – Skurmedel Aug 12 '09 at 11:58
  • 3
    No, it is not a particularly strict rule. It is the easiest way to completely avoid memory leaks, What limitations are you thinking about? – grimner Aug 12 '09 at 12:05
  • 1
    No, not from the standard, but from application code. Pointer manipulations belong inside libraries only. – grimner Aug 12 '09 at 12:19
  • 5
    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 Aug 12 '09 at 12:28
  • 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 Aug 12 '09 at 13:16
  • 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 Aug 12 '09 at 13:17
2
  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 benefit does not outweigh the cost.

gd1
  • 11,300
  • 7
  • 49
  • 88
JohnMcG
  • 8,709
  • 6
  • 42
  • 49