Why "vector.erase()" (in C++) is not behaving as expected?
Asked Answered
G

2

6

I have written a simple program to test "vector.erase" feature. There is a simple class (MyClass0) which writes some related message in it's constructor and another in it's destructor. And then there is a vector which contains 4 objects of type MyClass0. As I erase the second element of the vector:

    vec0.erase(vec0.begin() + 1);

I suppose that the Message "GoodBye From 2" should be outputted on the screen. But the message "GoodBye From 4" is shown. It seems that the destructor of the 4'th element of the vector is called. (Although it is not the case, because the 4'th element will be destructed at the end, when the "main" is finished). can anyone help me please so that I can find out the reason. The code and the output which is shown on the screen are:

Code:

#include <iostream>
#include <vector>

using std::cout;
using std::endl;

class MyClass0
{
public:
    MyClass0(int i_i_) : i_(i_i_)
    {
        cout << "Hello From " << this->i_ << endl;
    }
    ~MyClass0()
    {
        cout << "GoodBye From " << this->i_ << endl;
    }
    std::string MyToString()
    {
        return std::string("This is ") + std::to_string(this->i_);
    }
private:
    int i_;
};


int main()
{
    std::vector<MyClass0> vec0 = { MyClass0(1), MyClass0(2), MyClass0(3), MyClass0(4) };
    cout << endl << "Before erasing..." << endl;
    vec0.erase(vec0.begin() + 1);
    cout << "After erase" << endl << endl;

    return 0;
}

Output on the screen:

Hello From 1
Hello From 2
Hello From 3
Hello From 4
GoodBye From 4
GoodBye From 3
GoodBye From 2
GoodBye From 1

Before erasing...
GoodBye From 4
After erase

GoodBye From 1
GoodBye From 3
GoodBye From 4

https://godbolt.org/z/qvrcb81Ma

Genus answered 17/11, 2022 at 13:16 Comment(3)
Maybe add copy constructor and assignment operator, and have those also output some friendly messages.Butler
You didn't follow the rule of 3 (or rule of 5), so you failed to detect move and copy construction and assignment.Gaffe
This doesn't address the question, but you can remove all of those this->s. They're just noise. Also, this program doesn't need the extra stuff that std::endl does; '\n' ends a line.Revalue
T
9

Her is your code modified a bit

class MyClass0
{
public:
    MyClass0(int i_i_) : i_(i_i_)
    {
        cout << "Hello From " << this->i_ << endl;
    }
    ~MyClass0()
    {
        cout << "GoodBye From " << this->i_ << endl;
    }
    std::string MyToString()
    {
        return std::string("This is ") + std::to_string(this->i_);
    }
    MyClass0(const MyClass0& other) : i_{other.i_}
    {
        std::cout << "Copy construct " << i_ << '\n';
    }

    MyClass0& operator=(const MyClass0& other)
    {
        std::cout << "Asign " << other.i_ << " onto " << i_ << '\n';
        i_ = other.i_;
        return *this;
    }
private:
    int i_;
};

What exposes what actually happens: https://godbolt.org/z/hW177M7o6

When vector removes item from a middle it assigns items using operator= shifting them to the left and then deletes last item.

Tallinn answered 17/11, 2022 at 13:27 Comment(2)
Thanks a lot for your response. I have understand now, what the reason was. But there would be then another problem: why the destructor of the erased element (second element) is never called? As it can be seen in the output, the term "GoodBye From 2" is not shown anywhere.Genus
It is destroyed, but when it happens it contains different value (so it prints 3), since assign operator was used on it. You can add to logs this value to see that.Tallinn
T
8

A vector is not allowed to have any holes in the middle. That means when you erase the second element you don't actually remove it. What happens is all of the elements get moved forward to fill in the hole, and after that the last element in the vector can be removed as it has been moved forward once

//start with
1 2 3 4

// erase 2, so move 3 into 2 and 4 into 3
1 3 4 *

// * is old 4 and we don't need that so remove it from the collection
1 3 4

// removing * calls the destructor for that element
Tactic answered 17/11, 2022 at 13:23 Comment(4)
Thanks a lot for your response. I have understand now, what the reason was. But there would be then another problem: why the destructor of the erased element (second element) is never called? As it can be seen in the output, the term "GoodBye From 2" is not shown anywhere.Genus
@Genus Because erase doesn't destroy the second element. It instead assigns element 3 to element 2, so now element 2 has the value of element 3. It does this with element 4 to element 3, making element 3 have a value of 4, and then it removes the element 4 that was just assigned to element 3 to correct the size of the vector.Tactic
But it is written in multiple websites that the destructor of the erased element will be called, such as in this website The question in this website is about a vector of pointers, but many answers have mentioned that the destructor will be called, if the element is not a pointer.Genus
@Genus And the destructor did get called for the actual removed element. It's just happens that when you removed from the middle you aren't actually removing that elements, as vectors are not allowed to have holes. Instead you move everything forward and delete from the end.Tactic

© 2022 - 2024 — McMap. All rights reserved.