Accessing list element pointed by an iterator - c++

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).

Related

C++ Remove Objects in List at Loop

How can i delete all objects which are works finished
I using the following code but get list iterator not incrementable
How can I remove it without deleting it
list<A*> myList;
for(list<A*>::iterator it = myList.begin(); it !=myList.end(); ++it ){
(*it )->DoSomething();
if((*it )->WorksFnished()){
//myList.erase(it ); <- It's works but I get exception after the loop
//myList.remove(*it ); <- It's works but I get exception after the loop
}
}
erase returns an iterator
list<A*> myList;
list<A*>::iterator it = myList.begin();
while( it != myList.end() ) {
(*it)->DoSomething();
if( (*it)->WorksFnished() ) {
it = myList.erase(it);
} else {
++it;
}
}
You can make use of the fact that erase returns a new iterator, as described in other answers here. For performance-critical code, that might be the best solution. But personally, I would favor splitting the loop into separate processing and removal steps for readability and clarity:
// Assumes C++ 11 compatible compiler
list<A*> myList;
// Processing
for(const auto* each : myList){
each->DoSomething();
}
// Deletion
myList.remove_if([](A* each) {
return each->WorksFnished();
});
If you don't want to use remove_if, some alternatives are:
Copy all objects you want to keep into a new list, then std::swap it with your current list
Use a temporary list toBeRemoved, and add all objects that should be removed to that. When you're finished iterating over the actual list, iterate toBeRemoved and call myList.erase for each element
Some workaround..
increment the number of objects from the list that has WorkFnished.
then after the loop. if the accumulator match the list size, clear it.
size_t nFinished = 0;
list<A*> myList;
for(list<A*>::iterator it = myList.begin(); it !=myList.end(); ++it ){
(*it )->DoSomething();
if((*it )->WorksFnished()){
nFinished++;
}
}
if (nFinished == myList.size())
{
myList.clear();
}
If you use erase you have to assign it back to the iterator. In this case, we have to take care of the incrementing ourselves depending if the current element was erased or not.
list<A*> myList;
for (auto it = myList.begin(); it != myList.end(); )
{
(*it)->DoSomething();
if( (*it)->WorksFnished() ) {
it = myList.erase(it); // Sets it to the next element
} else {
++it; // Increments it since no erasing
}
}
std::list::erase
Return: An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.

deallocating memory in a map with pointers

I was trying to erase pointer elements (the value in the map is a pointer) from the map and I saw the code here What happens to an STL iterator after erasing it in VS, UNIX/Linux?
for(map<T, S*>::iterator it = T2pS.begin(); it != T2pS.end(); T2pS.erase(it++)) {
// wilhelmtell in the comments is right: no need to check for NULL.
// delete of a NULL pointer is a no-op.
if(it->second != NULL) {
delete it->second;
it->second = NULL;
}
}
I am not sure if the 'delete it->second' with de-allocate the correct memory because the erase(it++) step already moves the iterator to the next object. By the time, it reaches the delete statement, it is pointing to the next element which we don't want to delete. Am I missing something?
I believe this will work as expected.
The third section of the for loop (where the iterator is erased and then incremented) executes after the first iteration, and so on for each relevant iteration. Thus, you're always erasing the element you've already "dealt with" in the loop contents.
A parallel example:
for (int i = 0; i < 1; ++i) { ...
You will still enter the loop and execute with i = 0 before incrementing i and checking the looping condition.
You may want to try another way:
while (T2pS.size() > 0) {
if (T2pS.begin()->second != NULL) {
delete T2pS.begin()->second;
}
T2pS.erase(T2pS.begin());
}

Erasing an element from a list container

I am having difficulty understanding why the code is behaving this way. First of all I have read the relevant answered material and still found the explanations abit advanced. So I'm wondering if some-one could explain this in a simple fashion.
Ok, so I am erasing elements from a list.
The list contains int elements that are both odd and even numbers. This part I understand.
Here is the code I originally wrote to remove the odd numbers from the list
for(list<int>::iterator i = lNo.begin(); i != lNo.end(); i++)
{
if(*i%2 == 0 )
{
lNo.erase(i);
}
else
{
cout << " " << *i;
}
}
With this code, the program simply does not compile, and I read a message stating that the program has to shut down.
The erase function works when I write this code:
for(list<int>::iterator i = lNo.begin(); i != lNo.end(); i++)
{
if(*i%2 == 0 )
{
i = lNo.erase(i);
}
else
{
cout << " " << *i;
}
}
I just need to uderstand why the program works when I code i = lNo.erase(i) and not with just lNo.erase(i)?
A simple concise answer would be much appreciated.
I know that different containers have different constraints, so which constraint did I violate with the original piece of code?.
As stated in the documentation, the erase function invalidates the iterator passed in. That means it cannot be used again. The loop cannot proceed with that iterator.
The documentation also states that it returns an iterator to the element that was after the erased one. That iterator is valid and can be used to proceed.
Note however that since it returns an iterator to the element after the one that was erased, there is no need to increment that to advance, or that element will not be checked for oddness. The loop should catter for that and only increment when no erasure was done.
Even your second code is incorrect.
The correct code should be this:
for(list<int>::iterator i = lNo.begin(); i != lNo.end(); /*NOTHING HERE*/ )
{
if(*i%2 == 0 )
{
i = lNo.erase(i);
}
else
{
cout << " " << *i;
++i; //INCREMENT HERE, not in the for loop
}
}
Note that erase() erases the item and returns the iterator to the next item. That means, you don't need to increment i in your code when you erase; instead you just need to update i with the returned value from erase.
You could use erase-remove idiom as:
lNo.erase(std::remove_if(lNo.begin(),
lNo.end(),
[](int i) { return i%2 == 0; }),
lNo.end());
Live demo
The thing is that you're using an iterator that doesn't expect the chaining of your list to be modified.
So when you're calling erase() on your list, the chaining is effectively modified and so your iterator isn't valid anymore. The i++ statement doesn't work anymore.
But, in the 2nd version, you re-assign your iterator to valid object that still have the chaining intact, so the i++ statement can still work.
In some framework, you have 2 kinds of iterators, the kind that do reflect immediately what's happening to the underlying dataset (here is what you're using), and the kind that doesn't change their chaining whatever happening to the underlying dataset (so you don't have to use the weird trick of the 2nd version).

Vector iterator not dereferenceable when changing iterator comparisson

The following code snippet gives the error message at run time of:
Debug assertion failed: Vector iterator not dereferenceable
for(it=stat1vec.begin(); *(it)>=investigated_stat; it++, positioner++)
{
if(*it==investigated_stat)
equalwith++;
}
When changed to:
for(it=stat1vec.begin(); *(it)==investigated_stat; it++, positioner++)
{
if(*it==investigated_stat)
equalwith++;
}
The code works perfectly.
All the has been done is to change >= to ==
Why is this?
Thanks very much
You should test for the iterator to be a valid iterator before dereferencing it. Your loop will continue until *it < investigated_stat but it won't stop when none of the elements are >= investigated_stat and you iterate past the end of the vector.
The test in the for loop should be
it != stat1vec.end() && *it >= investigated_stat
Which makes the slightly modified for:
for(it = stat1vec.begin(); it != stat1vec.end() && *it >= investigated_stat; ++it, ++positioner)
if(*it == investigated_stat)
++equalwith;
It seems at some point before reaching the end of the vector you assigned zero to *(it): at this point the loop terminated. The logical comparison tried to find something different - and failed. I assume you meant to write == in the second loop as well.
Note that the idiomatic way to iterate over a sequence (when not using algorithms, that is) looks something like this:
for (it = v.begin(), end = v.end(); it != end /* possible additional conditions */; ++it)
{
...
}

C++ multimap iterator invalidation

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.