unordered_map erase segfault

3.9k views Asked by At

Recently I have found this weird behavior of unordered_set caused by erase method. I present the minimal example below.

First I create an unordered_set. Then I erase one of the element, say France. Then I erase every element with a for loop. Upon execution, it segfaults. However, if I comment out the erasing France part, then the code works fine.

This program is compiled with g++ test.cpp --std=c++11 . The version of g++ is 4.9.1.

#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
  std::unordered_set<std::string> myset =
  {"USA","Canada","France","UK","Japan","Germany","Italy"};

  // erasing by key, causing segfault later; no segfault if commented out
  myset.erase ( "France" );                         

  std::cout << "myset contains:";
  for ( const std::string& x: myset ) { myset.erase(x); }

  // The problem persists for a regular for loop as well. 
  //for (  std::unordered_set<std::string>::iterator it = myset.begin(); it!=myset.end(); it++  ) { myset.erase(it); }

  std::cout << std::endl;

  return 0;

}

Does anyone have a clue?

Thanks, KC

1

There are 1 answers

2
Jonathan Potter On BEST ANSWER

Erasing elements inside a range-based for loop is undefined behavior. When you erase an element in a set, iterators to that element are invalidated, and behind the scenes the compiler uses the current element's iterator to progress to the next element. Range based for is equivalent to:

auto && __range = range-init;
for ( auto __begin = begin-expr(__range),
   __end = end-expr(__range);
   __begin != __end;
   ++__begin ) {
   for-range-declaration = *__begin;
   statement
}

At the time ++__begin is called, the element has been erased and the iterator is invalid.

Edit: Here's an example of how you could do this correctly:

auto it = myset.begin();
while (it != myset.end()) { it = myset.erase(it); }

In C++11 the erase method returns a new iterator, so this avoids incrementing the old iterator after the element it points to has been erased. But also please note that this code is pretty meaningless, unless it's just an experiment. If you just want to clear a set's contents, call myset.clear().