When do I have to delete a pointer?
Asked Answered
M

4

25

When must I use delete? For example, say I'm allocating two objects:

Fraction* f1 = new Fraction(user_input1, user_input2);
Fraction* f2 = new Fraction(user_input3, user_input4);

The next time I want to use the new operator to create a new object, do I have to delete first? I am confused because I am used to having the garbage collector in Java take care of objects and their deletion. Do I have to delete before using new again?

if (f1) delete f1;
if (f2) delete f2;

f1 = /* ... */;
f2 = /* ... */;
Marigolda answered 2/11, 2014 at 2:24 Comment(2)
Just fyi, a compelling question to likewise-ask is whether you should even be using new in the first place. Add that to the list of things to consider.Maskanonge
It’s a conceptual error to think about “deleting a pointer”. The delete operator doesn’t delete the pointer, it deletes the object the pointer is currently pointing at.Coz
J
35

Rather then telling you when to use delete, I'll try to explain why you use pointers anyway. So you can decide when to use dynamic objects, how to use them and so when to call delete (and not).


Rules of thumb:

  • Use static objects where possible, then when needed create a pointer to that instance. No delete call needed.
  • If you create a pointer to a dynamic object, create clean up code. So when you write new also write delete somehwere at a suitable location (and make sure that is called).
  • For every new keyword there needs to be a delete keyword. Otherwise you are taking all the resources the machine has resulting in applications to crash or just stop. Also it will make the system slower.

Static creation of an object:

Fraction f1;
  • No need to delete anything, that is handled when exiting the scoop it is created in.

Dynamic creation of an object:

Fraction* f1;

Now you have this address to a memory block on the heap. It is an invalid one since you haven't assigned anything to it. Good practice would be - depending on where you declare it - to assign it a NULL (windows) or 0 (cross-platform).

Fraction* f1 = 0;

When to use delete

As soon as you create a dynamic object, thus calling the new operator, you need to call delete somewhere.

int main()
{

    Fraction* f1 = 0;    // Good practise to avoid invalid pointers
                         // An invalid pointer - if( f1 ){ Access violation }

    f1 = new Fraction(); // Could have done this at the previous line

    /* do whatever you need */

    if( f1 )
    {

        delete f1; 
        f1 = 0;          // not needed since we are leaving the application
    
    }

    return 0;

}

In some scenarios it could be useful to have an array of Fraction, or pointers to it. Using an int for simplicity here, same as skipping error handling:

int arr[ 10 ];
int cur = -1;
int* Add( int fraction )
{
    arr[++cur] = fraction;
    return &arr[cur];
}

// Usage:
Add( 1 );
Add( 4 );

One thing happening here, no assignment to any memory through dynamic objects. They are freed automatically. The pointer returned by the function is a pointer to a static memory block.

When making the arr as pointers to int:

int* arr[ 10 ];
int cur = -1;
int* Add( int* fraction )
{
    arr[++cur] = fraction;
    return arr[cur];
}

// Usage:
int* test;

test = Add( new int( 1 ) );
test = Add( new int( 4 ) );

Now you have to memory blocks which are leaking since you have no clean up code.

When you call after each Add(...) the delete test, you have cleaned up the memory but you have lost the values you had stored within int* arr[ 10 ] as they are pointing to the memory holding the value.

You can create another function and call this after you are done with those values:

void CleanUp()
{
    for( int a = 0; a < 10; ++a )
        delete arr[ a ];
}

Small usage example:

int* test;
int  test2;

test  = Add( new int( 1 ) );
test2 = *Add( new int( 4 ) ); // dereference the returned value

/* do whatever you need */

CleanUp();

Why do we want to use pointers:

int Add( int val )
{
    return val; // indeed very lame
}

When you call a function that needs a parameter (type), you are not passing in the instance but rather a copy of it. In the above function you are returning a copy of that copy. It will amount to a lot of duplication all memory involved and you make your application tremendously slower.

Consider this:

class Test
{
    int  t;
    char str[ 256 ];
}

If a function needs a type Test, you are copying the int and 256 chars. So make the function so it needs only a pointer to Test. Then the memory the pointer is pointing to is used and no copying is needed.

int Add( int val )
{
    val++;
    return val;
}

Within this last example, we are adding 1 to the copy of val and then returning a copy of that.

int i = Add( 1 );

result: i = 2;

void Add( int* val )
{
    // mind the return type
    *val++;
}

In this example you are passing the address to a value and then - after dereferencing - adding one to the value.

int i = 1;
Add( &i );

result: i = 2;

Now you have passed in the address to i, not making a copy of it. Within the function you directly adding 1 to the value at that memory block. You return nothing since you have altered the memory itself.


Nulling/testing for valid pointers

Sometime you encounter examples such as:

if( p != 0 ) // or if( p )
{
    /* do something with p */
}

This is just to check if the pointer p is valid. However, an invalid address - thus not pointing to a memory you have reserved (the access violation) - will pass through too. For your code, an invalid pointer is a valid address.

Therefore, to use such a check you have to NULL (or 0) the pointer.

Fraction* f1 = 0;

When f1 == 0, it doesn't point to anything otherwise it points to whatever it points to.

This is useful when you have a pointer in a 'main'-class which is or isn't created.

class Fraction
{
    public:
    int* basicFeature;
    int* ExtendedFeature = 0; // NULL this pointer since we don't know if it
                              // will be used
    Fraction( int fraction )
    {
        // Create a pointer owned by this class
        basicFeature = new int( fraction );
    }
    Fraction( int fraction, int extended ) // mind the static
    : Fraction( fraction )
    {
        // Create a pointer owned by this class
        ExtendedFeature = new int( extended );
    }
    ~Fraction()
    {
        delete basicFeature;
        if( ExtendedFeature )
            // It is assigned, so delete it
            delete ExtendedFeature;
    }
}

withing the constructors we are creating the two pointers, so within the destructor we are cleaning up those pointers. Only checking the ExtendedFeature since this one may or may not be created. basicFeature is always created.

You could replace the if statement including its scope within the destructor by calling a new function: removeExtendedFeature() where that function implementation would be:

Fraction::removeExtendedFeature()
{
    if( ExtendedFeature )
    {
        // It is assigned, so delete it
        delete ExtendedFeature;
        // Now it is important to NULL the pointer again since you would
        // get an access violation on the clean up of the instance of 
        // the class Fraction
        ExtendedFeature = 0;
    }
}

And the new destructor:

Fraction::~Fraction()
{
    delete basicFeature;
    removeExtendedFeature();
}

Another functionality of nulling could be:

int Fraction::getValue()
{
    int result = *basicFeature;
    if( ExtendedFeature )
        result += *ExtendedFeature;
    return result;
}

My apologies for the lame class Fraction, with an ever more lame extended feature. But as an example it would serve the purpose.

Jeremyjerez answered 2/11, 2014 at 9:54 Comment(1)
Some do advise to delete everything is the reversed order compared to using the new operator: as in the last example of Fraction, I did it wrong in the dtor. int one = new int(); int two = new int(); delete two; delete one; Don't know why exactly that should be other then 'hierachable' timelines. @Anybody: please fill in this cap of mine.Jeremyjerez
R
13

There are two main ways to create C++ options. One is on the stack (i.e. Fraction f1;), and that memory is automatically freed when that stack frame is popped. The second is on the heap (i.e. Fraction* f1 = new Fraction();. The key is the new keyword.

The basic summary is this: your news and deletes must match up. Every time you new something, you must delete it when you are done with it. "When you are done with it" is up to you to determine. However, if you reuse a variable (see below), you will need to delete first, otherwise you will have no way to get the original object back to delete.

Fraction* f1 = new Fraction(); // create memory on heap, will need to free
f1 = new Fraction(); // this is a memory leak because I didn't first free the
                     // original f1 object, which I can no longer access
Raseda answered 2/11, 2014 at 2:28 Comment(1)
This is the easiest answer to understandOndrej
B
8

The rule of thumb is every new must have a corresponding delete.

Manually using new and delete isn't a common thing to do in C++. When you initialize things without using new or delete it is guaranteed to be dealt with for you by the upcoming }. Assuming everyone's doing their job and following RAII principles when it comes to objects.

Fraction f1(...);

instantiates a Fraction object called f1. Its destructor will get called upon reaching the end of scope.

The 'modern' approach is to handle things like above. In the rare instances where this approach wont work, you should be using smart pointers.

Bottom answered 2/11, 2014 at 2:28 Comment(0)
V
3

You need to delete the object the pointer points to.

Then you can create with new another object, but don't forget to delete it later. :)

Do you really have to check before deleting? Make sure you read this answer for this, which actually says no. Moreover, a good practise is to set the pointer (after the delete) to NULL, for future use.


So, what's gonna happen if you use twice new on the same pointer?

Recall that new will allocated as much memory as we ask it for. Then, we need to know where is this memory. For this purpose, new returns a pointer to that memory.

So, let's say you do this:

f1 = new Fraction(user_input1, user_input2);
f1 = new Fraction(user_input1, user_input2);
delete(f1);

Is it OK? NO.

  1. The first new allocates some memory that f1 points to.
  2. The second new will overwrite the value of the pointer, resulting in f1 pointing to new chunk of memory we allocated.
  3. Then we use delete and the memory was de-allocated...but wait, which memory? The one the second new gave us.
  4. What about the memory coming from the first new? We can't access it, since we didn't keep a pointer to that block of memory.
  5. That means we have a memory leak, which is a common mistake in c++.

Remember

you should do as many deletions as the new keywords you have used!


As a comment by WhozCraig, you should choose wisely between dynamic creation of objects (require new and delete) and the static creation of objects, which is done automatically.

Nice answers are here and here about this topic.

Viscus answered 2/11, 2014 at 2:28 Comment(11)
@Marigolda make sure to check the link in my update, especially since you are planning to use the pointers in the future. :)Viscus
Exactly what the link says @Galik. (but I will make it more obvious) :)Viscus
Would the downvoter be so kind to explain why? :) SO that I can improve the answer and my future answers. :)Viscus
Testing a pointer before deleting it (or free()ing it) is redundant. It is akin to ensuring an unsigned variable isn't negative. It's simply not necessary.Johnnyjohnnycake
Exaclty what the link in my answer says @dreamlax. Moreover, I have already updated the answer to state that the link says that this is not necessary. Don't you agree?Viscus
@G.Samaras: You posted a link saying it's not necessary contradicting what you stated earlier with "you should do this".Johnnyjohnnycake
Oh damn, I see your point @dreamlax, I wanted to say that if you use an if statement, then you should this. Updating right away, thanks!Viscus
This makes so much more sense now. Thanks!Marigolda
Really glad I helped @qnob, even though I got downvoted. Helping is what matters. ;) Since your question was answered, you should probably accept one of the given answers, so that the question appears as answered in the question feed.Viscus
@G.Samaras: Excellent, now I can upvote (for the record, it was not me who downvoted)Johnnyjohnnycake
Thank you @dreamlax, not for the upvote, but for improving my answer. The point is helping the OP and future users, not the reputation, as I feel you already know.Viscus

© 2022 - 2024 — McMap. All rights reserved.