-2

please try to compile and run the following code. When iterating over a vector of shared pointers, I have to delete last element, this will result in a segmentation fault, but I can't understand why the for iteration doesn't break when el_it reaches v.end(), and I have to do it manually (commented code).

#include <vector>
using std::vector;

#include <memory>
using std::shared_ptr;

#include <algorithm>
using std::remove;

class A {
public:
    A(int age) { age_ = age; }
    int age_ = 0;
    int alive_ = 1;
};

int main() {

    shared_ptr<A> a0(new A(0));
    shared_ptr<A> a1(new A(1));
    shared_ptr<A> a2(new A(2));
    vector< shared_ptr <A> > v;
    v.push_back(a0);
    v.insert(v.end(), a1);
    v.insert(v.end(), a2);
    for (auto el_it = v.begin(); el_it != v.end(); ++ el_it) {
        auto el = *el_it;
        if (el->age_ == 2) {
            v.erase(el_it);
        }
        /*
        if (el_it == v.end()) // Why is this required ??
            break;
        */

    }
    return 0;

}

2 Answers2

1

Change:

v.erase(el_it);

To

el_it = v.erase(el_it); // don't do this yet, read below

Erasing invalidates iterators. The return value of erase is the valid iterator to the next element. So with what I wrote above you're skipping one element. I'd recommend this:

for (auto el_it = v.begin(); el_it != v.end();) {
    auto el = *el_it;
    if (el->age_ == 2) {
        el_it = v.erase(el_it);
    } else {
        ++ el_it;
    }
}
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
1

Calling std::vector::erase() invalidates your el_it iterator. You are not taking that into account. erase() returns a new iterator to the element that follows the one you have removed. You need to use that new iterator to continue the loop.

Try this instead :

for (auto el_it = v.begin(); el_it != v.end();) {
    auto el = *el_it;
    if (el->age_ == 2) {
        el_it = v.erase(el_it);
    } else {
        ++el_it;
    }
}

Alternatively, a better option is to use the erase-remove idiom via the std::remove_if() algorithm, instead of looping manual at all:

v.erase(
    std::remove_if(v.begin, v.end(),
        [](auto el){ return (el->age_ == 2); } 
    ),
    v.end()
);

C++20 adds std::erase_if() to make it easier:

std::erase_if(v,
    [](auto el){ return (el->age_ == 2); } 
);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770