简体   繁体   中英

Does C++ std::map and std::set erase copy values and thus invalidate iterators

I'm considering the implementation of removal in binary search tree. I know that std::map and std::set are usually implemented as an RBTree. But regardless, if it is some BST then when erasing/removing a node that has 2 children we generally swap it with its successor (or predecessor). Many textbooks show this as just copying over the successor's key and value. But this copy is not transparent. For example, if the value is another container to which I have outstanding iterators, then removing some OTHER key,value could now invalidate the iterators I have to a container that I have not explicitly changed. Rather than copying key,value we could just relink the successor node into the tree where the node is that should be deleted. This would maintain the state of the node.

Suppose I have a map of vectors:

std::map<int, vector<int> > mymap;
// Add root
mymap.insert(make_pair(2, vector<int>()));
// Add left child
mymap.insert(make_pair(1, vector<int>()));
// Add right child
mymap.insert(make_pair(3, vector<int>()));
mymap[3].push_back(10);
// I get an iterator to the vector stored in the node with key=3
vector<int>::iterator it = mymap[3].begin();

// I now remove element 2 from the map.  
mymap.erase(2);

// If the successor's (i.e. 3's) key,value were *copied* to 2's node
// and then 3's node was deleted this would invalid iterators 
vector<int>::iterator it2 = mymap[3].begin();

// These could/should be different if remove copied the successor
cout << &*it << " " << &*it2 << endl;

Certainly the node containing 3 could just be "relinked" within the tree in place of 2's node and 2's node deleted. This would maintain the correct state of the key,value.

I coded up this test case and it appeared to relink rather than copy. Is that behavior defined anywhere?

Similarly if I have a vector of vectors (ie vector > and I hold an iterator to an element in one of the "inner" vectors but then the outer vector resizes due to insertion of another vector then my iterator is invalid even though I haven't technically modified that inner vector. Is that "transitive" property of invalidation spelled out somewhere or is it "obvious"/"implied" and I'm just slow to the game?

The erase operations of the node-based containers (maps, sets, lists) invalidate only iterators and references to the element that was erased, but not any other iterators.

When you erase by value, this is usually straight-forward, but when you erase by iterator, this does require care in the common case of iterating over the entire container in a loop. To wit:

for (auto it = my_set.begin(); it != my_set.end(); )
{
     if (should_we_erase(it)) { my_set.erase(it++); }
     else                     { ++it;               }
}

Pay close attention how we make sure to not use the iterator after we erased it! In the erasing branch, we first set it to a new valid iterator one further via the post-increment, and then erase at the original iterator (which is the value of the post-increment expression). Alternatively, we can write this as it = my_set.erase(it); , since the erase operation returns the next (valid) iterator. A very common novice mistake is to put ++it int he loop header, which then ends up performing arithmetic on the invalid iterator.

By contrast, vector and deque invalidate a lot more iterators and references during a lot more operations.

The invalidation of iterators and references is well documented as part of the standard library specification, and any self-respecting documentation or tutorial will make it very clear when and how something is invalidated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM