Search code examples
c++iteratorsetc++17stdset

Why does std::set.erase(first, last) impact the container from which (first, last) were taken?


I will preface that I'm fairly new to C++ in general. Recently, I ran into some perplexing behavior from the std::set.erase() method, which I have isolated into the following code. This code sample crashes with a segmentation fault when it hits the second for loop:

#include <set>
#include <iostream>

int main() {
    std::set<int> foo = {0, 1, 2};
    std::set<int> bar = {0, 1, 2, 3, 4};
    for(int i : foo) {
        printf("%d\n", i);
    }
    bar.erase(foo.begin(), foo.end());
    for(int i : foo) { //Crash happens right here, before entering the body of the loop.
        printf("%d\n", i);
    }
    return 0;
}

Whereas if you remove the call to erase() it does not crash.

This is somewhat surprising behavior. Calling bar.erase() is obviously supposed to modify "bar" but I would not generally expect it to have any impact on how "foo" functions. What does std::set.erase() do that causes a segmentation fault to occur?

This is simple enough to bypass just by creating a copy of "foo" to supply start and end iterators to erase(), but I am curious why this behavior happens in the first place.


Solution

  • The arguments to the erase function are iterators. Iterators are not portable across objects. The iterators that you pass to bar.erase are supposed to be iterators on bar. If they're not, you get undefined behavior.

    The crash probably happened because the call to bar.erase somehow got rid of the contents of foo, because it used foo-based iterators, but foo thought they were still there.

    You should not create a copy of foo to provide iterators to bar.erase. You should use iterators based on bar.