Search code examples
c++c++17stdvector

std::vector - why does the element still exist after invoking delete?


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 ?


Solution

  • 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;
    }