C++ multimap iterator invalidation - c++

I'm trying to figure out how std::multimap iterators work, therefore I've created a simple example that shows the substance of my problem. If uncomment case 1, I expect iterator to point to the first element with the key 1, but in reality it prints all the values associated with key 0 (like nothing was erased) and sometimes it crashes, probably because iterator is invalid. However if uncomment case 2, all the values with key 1 are properly deleted.
Is there any way to know what is the next valid iterator for the multimap after erasure?
(for example std::vector.erase(...) returns one)
std::multimap<int, int> m;
for(int j=0; j<3; ++j) {
for(int i=0; i<5; ++i) {
m.insert(std::make_pair(j, i));
}
}
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
printf("%d %d\n", (*it).first, (*it).second);
++it;
if( (*it).second == 3 ) {
//m.erase(0); //case 1
m.erase(1); //case 2
}
}

The cause of the problem
When you call m.erase(0) in you example, it points at an element with the key 0 - so it is invalidated. m.erase(1) works, because when it is called the first time, it is not pointing to an element with the key 1, so it is not affected. In later iterations, no elements with the key 1 remain, so nothing is deleted, and no iterator is affected.
The Solution
multimap does not have an erase-method that returns the next valid iterator. One alternative is to call it = m.upper_bound(deleted_key); after the deletion. This is logarithmic, though, which might be too slow for your scenario (erase(x) and upper_bound would be two logarithmic operations).
Assuming you want to erase the key your iterator is currently pointing to, you could do something like this (otherwise, erase is fine, of course; not tested):
std::multimap<int, int>::iterator interval_start = m.begin();
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end(); ++it) {
if(interval_start->first < it->first) // new interval starts here
interval_start == it;
if( (*it).second == 3 ) {
std::multimap<int, int>::iterator interval_end = it;
while((interval_end != m.end()) && (interval_end->first == it->first)) {
++interval_end; // search for end of interval - O(n)
}
m.erase(interval_start, interval_end); // erase interval - amortized O(1)
it = interval_end; // set it to first iterator that was not erased
interval_start = interval_end; // remember start of new interval
}
}
This uses one linear operation, all the rest are constant time. If your map is very large, and you only have few items with equal keys, this will likely be faster. However, if you have many items with equal keys, the search for the end of the interval, is probably better done using upper_bound (O(log n) instead of O(n) when searching the end of the interval).

when you erase the iterator becomes invalid. instead remember the next element then erase:
std::map<int,int>::iterator next = m + 1;
m.erase
m = next;

First answer
std::multimap<int, int> m;
// ^^^^^^^^
std::map<int, int>::iterator it=m.begin();
// ^^^
Hum....
Second answer, re: edited question
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
.... stuff ....
m.erase(1); // container mutation
.... stuff ....
}
Be extremely careful when you are mutating a container (any container) when you are iterating on it, as you might invalidate an iterator you depend on.
The so-called "node-based containers" (list, set, map...) are the most robust container WRT iterator invalidation: they only invalidate iterators to deleted elements (there is no way for these iterators not be invalidated).
In this case you should check that the element you are about to delete isn't actually *it.
I am not quite sure what you are trying really to do with your loop.

From looking at your code, I think that your ++it is causing the problem. You are assigning it to a place that might have been deleted. move it to the end, after the if statement and test. like so:
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
printf("%d %d\n", (*it).first, (*it).second);
if( (*it).second == 3 ) {
//m.erase(0); //case 1
m.erase(1); //case 2
}
++it;
}

(Edited)
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
printf("%d %d\n", (*it).first, (*it).second);
++it;
if( (*it).second == 3 ) {
//m.erase(0); //case 1
m.erase(1); //case 2
}
}
In addition to invalidation of it iterator due to m.erase that may occur depending on the contents of multimap (already covered in another answer) there is always the problem that you dereference m.end() iterator on the last iteration of your for loop when you do if( (*it).second == 3 ) each time you run your program.
I suggest to run and debug with debug builds. I'm almost sure that every sane standard library implementation should contain assert to detect end() dereferencing.

Some guys above already have answered that you are playing with a fire.
Also, I think you are forgetting that multimap is ordered map, so you are iterating from the smallest keys to the largest ones. Therefore in the first case you remove keys after printing some of them, but in the second case you are remove just before going to them.

Related

Could you please see what is wrong with my itteration because i guess the problem is in that if from the nest [duplicate]

I have a vector that holds items that are either active or inactive. I want the size of this vector to stay small for performance issues, so I want items that have been marked inactive to be erased from the vector. I tried doing this while iterating but I am getting the error "vector iterators incompatible".
vector<Orb>::iterator i = orbsList.begin();
while(i != orbsList.end()) {
bool isActive = (*i).active;
if(!isActive) {
orbsList.erase(i++);
}
else {
// do something with *i
++i;
}
}
The most readable way I've done this in the past is to use std::vector::erase combined with std::remove_if. In the example below, I use this combination to remove any number less than 10 from a vector.
(For non-c++0x, you can just replace the lambda below with your own predicate:)
// a list of ints
int myInts[] = {1, 7, 8, 4, 5, 10, 15, 22, 50. 29};
std::vector v(myInts, myInts + sizeof(myInts) / sizeof(int));
// get rid of anything < 10
v.erase(std::remove_if(v.begin(), v.end(),
[](int i) { return i < 10; }), v.end());
I agree with wilx's answer. Here is an implementation:
// curFiles is: vector < string > curFiles;
vector< string >::iterator it = curFiles.begin();
while(it != curFiles.end()) {
if(aConditionIsMet) {
it = curFiles.erase(it);
}
else ++it;
}
You can do that but you will have to reshuffle your while() a bit, I think. The erase() function returns an iterator to the element next after the erased one: iterator erase(iterator position);. Quoting from the standard from 23.1.1/7:
The iterator returned from a.erase(q)
points to the element immediately
following q prior to the element being
erased. If no such element exists,
a.end() is returned.
Though maybe you should be using the Erase-remove idiom instead.
erase returns a pointer to the next iterator value (same as Vassilis):
vector <cMyClass>::iterator mit
for(mit = myVec.begin(); mit != myVec.end(); )
{ if(condition)
mit = myVec.erase(mit);
else
mit++;
}
If someone need working on indexes
vector<int> vector;
for(int i=0;i<10;++i)vector.push_back(i);
int size = vector.size();
for (int i = 0; i < size; ++i)
{
assert(i > -1 && i < (int)vector.size());
if(vector[i] % 3 == 0)
{
printf("Removing %d, %d\n",vector[i],i);
vector.erase(vector.begin() + i);
}
if (size != (int)vector.size())
{
--i;
size = vector.size();
printf("Go back %d\n",size);
}
}
As they said, vector's iterators get invalidated on vector::erase() no matter which form of iterator increment you use. Use an integer index instead.
You might want to consider using a std::list instead of a std::vector for your data structure. It is safer (less bug prone) to use when combining erasure with iteration.
Removing items from the middle of a vector will invalidate all iterators to that vector, so you cannot do this (update: without resorting to Wilx's suggestion).
Also, if you're worried about performance, erasing items from the middle of a vector is a bad idea anyway. Perhaps you want to use an std::list?

find() vs lower_bound+key_comp

I came across the following question in stackOverflow
std::map insert or std::map find?
why is using find() considered inferior to lower_bound() + key_comp() ?
assume I have the following map
map<int, int> myMap;
myMap[1]=1;
myMap[2]=3;
myMap[3]=5;
int key = xxx; //some value of interest.
int value = yyy;
the suggested answer is to use
map<int, int>::iterator itr = myMap.lower_bound(key);
if (itr != myMap.end() && !(myMap.key_comp()(key, itr->first)))
{
//key found.
// do processing for itr->second
//
}else {
//insert into the end position
myMap.insert (itr, map<int, int>::value_type(key, value));
}
why is it better than the following?
map<int, int>::iterator itr = myMap.find(key);
if (itr != myMap.end())
{
//key found.
// do processing for itr->second
//
}else {
//insert into the end position
myMap.insert (itr, map<int, int>::value_type(key, value));
}
In the second case, notice that if you need to insert the value, the iterator is always myMap.end(). This can not help to improve the performance of the insert operation (except when the new element is inserted at the end, of course). The container needs to find the correct position where to insert the new node, which is usually O(log N).
With lower_bound(), you already found the best hint for the container where to insert the new element and this is the optimization opportunity that the first technique offers. This might lead to a performance close to O(1). You have an additional key comparison, but that is O(1) as well (from the container's perspective).
Since both the initial find() and lower_bound are O(log N), you end up with a O(log N) plus two O(1) operation in the first technique and with two O(log N) operations in the second case.

Replace an element with old_value to new_value in vector - C++

I was going through some legacy code, and found out something that could be improved.
The vector has pointers to a class and all elements are unique in the vector, as per the design.
A function ReplaceVal replaces an element having old_value to a new_value in the vector, in the following fashion:
iterator i, i_e;
i = vector->begin();
i_e = vector->end ();
for (; i != i_e; ++i)
{
if ((*i) == old_child)
break;
}
// Insertion
vector->insert_call(new_child, i);
// Since, the pointers are invalidated, do another find for erase
i = vector->begin();
i_e = vector->end ();
for (; i != i_e; ++i)
{
if ((*i) == old_child)
break;
}
// Finally, erase the old_value
vector->erase_call(i);
So, essentially, this involves shifting of elements twice, each for insertion and erase, if you are inserting and erasing elements in the middle of the vector.
For n insertions and remove calls, the complexity is O(n*m), if m elements are shifted every time, on an average.
I think, this can be improved, if I use std::replace, as mentioned here # MSDN documentation and std_replace_example.
The complexity of the std::replace would be O(n) comparisons for the old_value and new_value & 1 assignment operation. It'd be as simple as:
replace (vector.begin( ), vector.end( ), old_value , new_value);
Please correct me, if I am wrong and share feedback on anything that I missed.
P.S. The insert and erase are custom calls, which also update pointers to left_sibling and right_sibling for a given element.
You don't even need to do that:
iterator position = std::find( vector->begin() vector->end(), old_child );
if ( position == vector->end() ) {
throw NoSuchElement();
}
*position = new_child;
should do the trick—no erase and no insert.
vector erase returns an iterator pointing to location of the element that followed the erased
iter = myvector->erase(i);
then you can use that iterator to insert.
myvector->inster(iter, new_value);
or the other way around. vector insert returns an iterator pointing to the inserted element
iter = myvector->inster(i, new_value);
myvector->erase(iter);
Why not use one of these?
std::set
std::multiset
std::unordered_set
std::unordered_multiset
Why you have such complexity? Is there any purpose. May be the vector is used somewhere else also, but you may also use sets along with vectors for searching.

Remove only one element from multimap with duplicate keys

I have a multimap with Note objects from which I want to remove only one object. There can be several Note objects with the same key. The problem is that right now there's also objects being removed that aren't within the key range I specify:
long key = note.measureNumber * 1000000 + note.startTime; // = 2000001
multimap<long, Note>::iterator it;
for (it = noteList.lower_bound(key); it != noteList.end() && it->first < (key + 1); it++) {
if(it->second.frequency == note.frequency){
noteList.erase(it);
}
}
When I run this code with key 2000001 the object I'm able to erase the right object, but another object with key 1000017 gets erased as well. Both objects have the same frequency though.
Any idea's what is wrong with my for loop?
EDIT: To be clear, I only want to check for objects with one specific key (in this case 2000001) there's no need for the iterator to look at objects with different keys than that one.
Calling erase() with the iterator will invalidate it, so you can't then continue to use it.
See Can I continue to use an iterator after an item has been deleted from std::multimap<>
Once you erase an iterator it becomes invalid. If you wish to erase from a map while you iterate through it, your code needs to change. Try this:
multimap<long, Note>::iterator it;
for (it = noteList.lower_bound(key); it != noteList.end() && it->first < (key + 1);) {
if(it->second.frequency == note.frequency){
noteList.erase(it++);
}
else
{
++it;
}
}
As already indicated here, erasing an iterator invalidates it.
I'd like to point out some inefficiency in code you have:
You don't need to iterate till the end of the loop. Consider this:
for (it = noteList.lower_bound(key); it != noteList.upper_bound(key) && it->first == key; it++)
{
if(it->second.frequency == note.frequency)
{
noteList.erase(it++);
}
else
{
++it;
}
}

Accessing list element pointed by an iterator

The natural answer would be to dereference the iterator and get the value. However, I'm stuck at using VC++ 2010 which doesn't allow dereferencing the list iterator (or does it?)
I'm confused because, at one point, I need to dereference two list iterators and compare their values using:
(*it) == (*it2)
The program crashes with an error, only due to this line. I'm also dereferencing the iterator in a statement:
printf("%d\n", (*it));
This works perfectly fine though.
So, is there any way to access an element without dereferencing or using a cliext::list.
for (it=sList.begin(); it != sList.end(); it++)
{
for (it2=it; it2 != sList.end(); it2++)
{
it2++;
if ((*it) == (*it2))
{
sList.erase(it, it2);
}
it2--;
}
}
The error I get is:
Debug Assertion Failed
Expression: list iterator not dereferencable
Surprisingly the same code runs without a problem when compiled on DevC++ (MinGW)
You can in fact dereference list iterators. If you couldn't, your comparison code wouldn't have even compiled. Most likely you're accidentally dereferencing an end iterator though rather than a valid one, causing the crash. Without more code it's hard to make further observations.
EDIT: I can't make out quite what it is you're trying to do. The code you've written erases all the elements between two equal elements. I'll assume you're actually trying to remove all the duplicate elements, and that sorting the list first for performance isn't a concern/option.
EDIT2: I saw in a comment below you really want to delete the range. Updated my code.
Then try something like this:
for (it=sList.begin(); it != sList.end(); ++it)
{
it2 = it;
++it2;
while(it2 != sList.end())
{
if ((*it) == (*it2))
{
it = it2 = sList.erase(it, it2); // Reset it as well since it will be blown away. It'll still point to the same value it did before though.
}
else
++it2;
}
}
Its surely your code. It has two problems as far as I can see. Checkout the comments.
for (it2=it; it2 != sList.end(); it2++)
{
it2++;
// there is no guarantee that it2 will now be valid
// it should be validated again
if ((*it) == (*it2))
{
// you should not modify the list here.
// this will invalidate your iterators by default.
sList.erase(it, it2);
}
it2--;
}
Try this instead:
for (it=sList.begin(); it != sList.end(); it++)
{
for (it2=sList.end()-1; it2 != it+1; it2--)
{
if ((*it) == (*it2))
{
it = sList.erase(it, it2)-1;
break;
}
}
}
This new version avoids two errors in the original version of the code. First, the code now properly handles the edge conditions of the inner for loop. In the original code, the for loop allowed it2 to go up to sList.end()-1, but then the next line incremented it to sList.end() on the last iteration. The next line then dereferenced this (invalid) iterator which is one past the last value of the list (because that's what end returns, it's not an iterator to the last value of the list).
Second, calling erase invalidates any iterators pointing to any of the values erased (which in this case would including any iterators from it to it2-1). By starting at the end of the list and working our way forward, we no longer have to continue iterating when we find the value, and can break from the inner loop once we find it. erase returns an iterator to the next element in the list after the elements deleted (which would be the next element we want to try for it). But since the for loop increments it, we subtract 1 from what's returned by erase so that it points to the right element once it's incremented at the beginning of the next loop iteration. (Note that in the case that it points to the first element, we actually temporarily set it to point an element before the beginning of the list; however, this is only temporary and we don't dereference the iterator while it's pointing outside the list).
Note that this preserves the original behavior of the code for the case 0 2 3 4 5 1 6 7 8 0 9 10 11 1. You haven't explicitly stated what order the deletes should occur (should the elements between 0's be erased first, or the elements between 1's, or do we need to add additional logic to actually erase the whole range except for the first 0 and 1?), but this code behaves like the original and erases the numbers in between the 0's and ignores the fact that the 9 10 11 afterwards was original in between matching 1's.
"select" Isn’t Broken.
It is rare to find a bug in the OS or
the compiler, or even a third-party
product or library. The bug is most
likely in the application. - from The
Pragmatic Programmer
It's highly likely due to your problem, not MS. Make it sure that your iterators are not invalidated while you are using them. You could accidentally erase the element which invalidate the iterator. Check this thread: What is the lifetime and validity of C++ iterators?
and Good Luck! :)
UPDATE:
As I mentioned earlier, you are invalidating your iterators by erasing them in the middle of the loop. See my code below to do it properly.
std::list<int>::iterator EraseElements(std::list<int>& sList, std::list<int>::iterator start)
{
for (std::list<int>::iterator itor1 = start; itor1 != sList.end(); ++itor1)
{
std::list<int>::iterator itor2(itor1);
++itor2;
for ( ; itor2 != sList.end(); ++itor2)
{
if ((*itor1) == (*itor2))
{
return sList.erase(itor1, itor2);
}
}
}
return sList.end();
}
void main()
{
// Test
list<int> sList;
sList.push_back(1);
// elements will be erased
sList.push_back(2);
sList.push_back(3);
//
sList.push_back(2);
sList.push_back(4);
sList.push_back(5);
// elements will be erased
sList.push_back(6);
sList.push_back(7);
//
sList.push_back(6);
list<int>::iterator next = sList.begin();
while (next != sList.end())
{
next = EraseElements(sList, next);
}
// It will print 1 2 4 5 6
for (std::list<int>::const_iterator itor = sList.begin(); itor != sList.end(); ++itor)
{
cout << *itor << endl;
}
}
It is really unclear what this code snippet or whatever code you get the error from is trying to do.
It appears what you want to do is for each item delete all items between it and the next matching item, or maybe it is the last matching item.
your inner loop iteration is double stepping from the loop increment and then incrementing again inside the loop.
your not checking if you have hit/passed the end of the list after doing the inner iteration which could lead to the crash when doing the comparison
after erasing you decrement it2, which then puts it before what it1 was (and is now deleted).