1

I have this code I don't understand why I need to delete and then pop_back().
Can I do it in 1 operation only ?

struct T
 {
    T(int n):x(n){};
    int print() {
       return x; 
    };
    private:
        int x;
 };

int main(int argv,char** argc)
{
    std::vector t = { new T(1),new T(2)};
    delete t.back();
    std::cout << t.size() << std::endl;
    std::cout << t.back()->print() << std::endl;

    t.pop_back();
    std::cout << t.size() << std::endl;
    std::cout << t.back()->print() << std::endl;

    return 0;
}

The output - as you can see after delete it looks like the vector is still holding the element witout the object:

2
179185600
1
1 

My question is why do I need delete and then remove , can't same operation be done in single command ?

3
  • 1
    @ynn and who's going to free the memory from the calls to new?? Commented Feb 3, 2023 at 8:20
  • 3
    Did you intend to create a vector of pointers? Commented Feb 3, 2023 at 8:25
  • 2
    you do not have to use new and delete. Whoever told you that was wrong. Commented Feb 3, 2023 at 8:28

3 Answers 3

10

1. The issues in your code:

This line:

delete t.back();

Is not removing any element from the std::vector. Your std::vector is holding pointers, and it will continue to hold the same pointers.
What the line above does is deallocate the object pointed by the element in the std::vector.

This line (the 1st occurance):

std::cout << t.back()->print() << std::endl;

Invokes UB (Undefined Behavior) because you derefernce a pointer after it has beed deleteed.

Then after:

t.pop_back();

The last pointer (the one that you deleteed, i.e. freed) is removed from the std::vector, and t.back() is the other pointer you inserted, which is still valid.


2. A different approach - avoid pointers:

Having wrote all that, in this case you do not actually need to deal with pointers at all.
You can use std::vector<T>, (instead of T* like you did) and store the elements by value. You will not need to new or delete anything.

Code example:

#include <vector>
#include <iostream>

struct T
{
    T(int n) :x(n) {};
    int print() { return x; };
private:
    int x;
};

int main(int argv, char** argc)
{
    std::vector t = { T{1}, T{2} };
    std::cout << t.size() << std::endl;
    std::cout << t.back().print() << std::endl;
    t.pop_back();
    std::cout << t.size() << std::endl;
    std::cout << t.back().print() << std::endl;
    return 0;
}

Output:

2
2
1
1

3. Using smart pointers:

If you need to use pointers, use smart pointers (like std::unique_ptr, std::shared_ptr) instead of raw ones. The benefit is that the deallocation is done automatically.
std::unique_ptr is the more light-weight and should be the default one. Use std::shared_ptr if you need shared ownership.

Code example (with the same output as above):

#include <vector>
#include <iostream>
#include <memory>

struct T
{
    T(int n) :x(n) {};
    int print() { return x; };
private:
    int x;
};

int main(int argv, char** argc)
{
    std::vector<std::unique_ptr<T>> t;
    t.push_back(std::make_unique<T>(1));
    t.push_back(std::make_unique<T>(2));
    std::cout << t.size() << std::endl;
    std::cout << t.back()->print() << std::endl;
    t.pop_back();
    std::cout << t.size() << std::endl;
    std::cout << t.back()->print() << std::endl;
    return 0;
}
12
  • so if i understand you right the delete only delete the object created , not the poiner which points to the object , right ?
    – user63898
    Commented Feb 3, 2023 at 8:26
  • Yes, that's correct.
    – wohlstad
    Commented Feb 3, 2023 at 8:27
  • Added an example how to avoid pointers at all in this case.
    – wohlstad
    Commented Feb 3, 2023 at 8:34
  • The problem is that this object is huge and i do not want to copy it by value on each operation , i like it to be created one in memory . also this object in the original code hold state which i like to manage in 1 palce
    – user63898
    Commented Feb 3, 2023 at 8:44
  • 1
    In this case you can use a smart pointer like unique_ptr.
    – wohlstad
    Commented Feb 3, 2023 at 8:45
3

In your code there are two distinct types of objects to be considered.

The elements in the vector are pointers to T, T*. The lifetime of those pointers is managed by the vector. You can add or remove them via member methods of the vector.

Then there are the objects you decided to manage manually via the raw pointers in the vector. They are instances of T. Because you used new to create them you must use delete to delete them.

The vector cannot know about this relation between the pointers (the elements in the vector) and the T objects. It was your decision to manage them manually. You do not have to do that.

Chances are high that you either want a std::vector<T>. Or if for some reason (there is none in your code) you need to have the Ts allocated dynamically, then you would use a std::vector<std::unique_ptr<T>>.


Now this line:

delete t.back();

deletes the T, but the pointer is still in the vector. It is an invalid pointer, the object is was pointing to is no longer alive. When you call t.back()->print() your code invokes undefined behavior. You cannot use an already deleted object. If you do, anything can happen. This has nothing to do with the vector, it is similar to

T* t = new T();   // create object
delete t;         // delete it 
t->print();       // use it -> !!! undefined behavior !!!

Instead of this you should write

T t;
t.print();

In case you are coming from Java, you must unlearn to use new to create objects. The way to create objects in C++ is not via new.

3
  • Tnx , what do you mean the way to create object not via new ? so via what ?
    – user63898
    Commented Feb 3, 2023 at 8:42
  • @user63898 like in the other answer. I considered to rewrite your code but it would be just the same as in the other answer. Commented Feb 3, 2023 at 8:43
  • 1
    @user63898 for now you can forget that new exists. It is very advanced, by the time you need it you will know. If you find a beginners tutorial that uses new [but not telling you how not to use it], you know that the tutorial is not worth its price Commented Feb 3, 2023 at 8:46
1

My question is why i need delete and then remove , can't same operation done in single command ?

There would be 6 answers from me to your question and Let this be your introduction to Pointers , Stack and the Heap.

1.) new operator "Allocates" memory on the heap not the stack,Create that Object (Using it's Constructors) then return a Pointer to the Object that is now on the Heap, hence the need for using the delete operator to "De-allocate" the memory you have requested and "Destroy" the created Object.

2.) delete "De-Allocates" the memory from heap and "Destroy" the created Object, and it does not remove it from the vector

3.)t.pop_back() will remove the Pointer element from the vector but will not de-allocate and Destroy the object the Pointer is Pointing too.

4.) It can't be done with one line since you have requested to allocate a Memory on the heap that can fit struct T inside via the new keyword. By using the keyword new the vector std::vector t is not storing an Object struct T,[std::vector<T>] instead storing Pointers to an Object of struct T [std::vector<T*>] since you did not explicitly Say what the Vector would Hold.

5.) Heap Allocated memory is perpetual , meaning it will always be there and labeled "In Use". Neither the Program, Runtime nor the O.S. can use it to allocate a new Memory Region on the heap unless you explicitly de-allocate and destroy it when using delete, Essentially saying "I'm done with this memory location thank you You may now destroy this object and re-use the memory)". or the Program Closes / Ends. So every new or new[] must be accompanied by delete or delete[] somewhere in the code when that variable is no longer in use anywhere in the program.

6.) After you understand the behavior of Heap / Dynamic Variables have a look at Smart Pointers. Which Handles the destruction of an object for you automatically. If you are coming from Managed Languages, you might want to read up on "Object Lifetimes" on C++.

The following Code will help you visualize my answer.

#include <vector>
#include <iostream>

struct T
 {
    T(int n):x(n){};
    int print() {
       return x; 
    };
    // Replaces the Value of X
    void SetX(int n){x=n;};
    private:
        int x;
 };

 int main(int argv,char** argc)
{

    // We explicitly Say that the Vector is holding a Pointers to an object struct T
    std::vector<T*> t;
    // Insert New Elements to the Vector
    t.push_back(new T(1));
    t.push_back(new T(2));

    // Will show that there are 2 elements in the vector.
    std::cout << "Size of Vector Is " << t.size() << std::endl << std::endl;

    // Assign the Last Element to a Pointer of T.
    T* pointerToT = NULL;
    pointerToT = t.back();

    // Print the Memory Address and value of pointerToT and the Last Element
    // And we can see they are the same location and holds the same value.
    std::cout << "The Memory Location of pointerToT is " << pointerToT << std::endl;
    std::cout << "The Memory Location of t.back() is " << t.back() << std::endl;
    std::cout << "The Value of X in pointerToT is " << pointerToT->print() << std::endl;
    std::cout << "The Value of X in t.back() is " << t.back()->print() <<std::endl << std::endl;
    
    //Removes the Last Element of the Vector
    t.pop_back();

    // Will show that there is only 1 element in the vector.
    std::cout << "Size of Vector Is " << t.size() << std::endl << std::endl;

    // Print the Memory Address of pointerToT and the Value of X
    // And as we can see pointerToT is still holding the same location
    // and still holding the value 2, since the program knows that
    // The memory Location is "Still" in use and not yet De-Allocated 
    // ready to be re-used and the object not yet destroyed.
    std::cout << "The Value of X after pop_back() is " << pointerToT->print() << std::endl;
    std::cout << "The Memory Location of pointerToT after pop_back is " << pointerToT << std::endl << std::endl;
    
    
    //De-Allocate the Memory and Destroy the Object that was created by t.push_back(new T(2));
    delete pointerToT;
    
    // Prints the value of X, that is stored in the memory on the heap that it's pointing to. 
    // This Value may or may not be 2 if it is now used other parts of the Code 
    // which we call an Undefined Behaviour.
    // On a Larger Program this could and would Cause a Segmentation fault or 
    // an Access Violation since this is now a Memory Region that is not explicitly
    // Allocated for pointerToT , to the Member Variable X, nor
    // The Last Element of the Vector and may now be in use by other variables and that the object is Destroyed already destroyed.
    std::cout << "The Memory Location of pointerToT after delete is " << pointerToT << std::endl;
    std::cout << "The Value of X after Delete is " << pointerToT->print() << std::endl;
    
    // This is also an undefined Behaviour since you are now accessing and modifying 
    // a Memory Location that is not Allocated for pointerToT . But would show 
    // that the value of X is now 10. So only delete(Destroy)when you are sure you are not going to be
    // accessing that variable again.
    pointerToT->SetX(10);
    std::cout << "The Value of X after SetX() is " << pointerToT->print() << std::endl;
    
    
    
    return 0;
}
7
  • 1
    fwiw, "stack" and "heap" are colloquial terms in this context. C++ never mentions them. Official terms are automatic vs dynamic storage duration. Commented Feb 3, 2023 at 11:09
  • 1
    also your explaination is not complete. From what you write there would be no difference to malloc and free, but there is. new not only allocates memory and delete not only deallocates memory. Commented Feb 3, 2023 at 11:11
  • @463035818_is_not_a_number You are Correct. Hence number 6, explaining the very nature of Dynamic Memory Allocation including the Nature of Object Destruction to someone who barely understands C++ at once would overwhelm them. Might even sway them to another language. And I did say the nature of new in number 1. You may have the Educational background for it but I do not, so forgive me for using the "Wrong Term" for Dynamic and Automatic Storage Durations. I have never read a book on Programming hence the "Ambiguity" on my terminologies. Commented Feb 3, 2023 at 13:20
  • using stack and heap as terms is so common, that was a minor nitpick. But imho your explanation of new and delete is not ok. It leaves out details that do not make the explanation simpler, but wrong. What it means to call a constructor must be learned befre calling new, hence I dont see the point of leaving it out (especially to a beginner). Commented Feb 3, 2023 at 13:36
  • @463035818_is_not_a_number When you put it that way, Yes I Did miss and it and Does make my answer wrong to the point of mistaking it for malloc and free. Leaving out the Construction via the Constructor and the Destruction via the Destructor is a Major Booboo. I edited my answer to reflect it. It's my First answer in S.O. and as someone who only had Experience in C++,Basic and VB. forgot that other Languages doesn't Share the same "Constructs" that C++ have. I may have taken that "Construct" for granted! Thank you for teaching me a thing or two about answering questions. Commented Feb 3, 2023 at 13:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.