Iterating over map and erasing element - c++

I have a global variable as a map and a function that iterates over the elements of this map such as:
void printMap(){
for ( auto it = MyMap.begin(); it != MyMap.end(); ++it ){
std::cout << it->second;
}
}
which works fine.
I want to add a functionality to the function which is after printing an element, it should be erased from the map like this:
void printMap(){
for ( auto it = MyMap.begin(); it != MyMap.end(); ++it ){
std::cout << it->second;
MyMap.erase(it);
}
}
However, by adding the erase line I got an exception error of this type:
Thread 2: EXC_BAD_ACCESS (code=1, address=0x20000002)
I tried another way which is like this:
void myFunction(){
printMap();
MyMap.clear();
}
but I also got the same exception
Thread 2: EXC_BAD_ACCESS (code=1, address=0x0)
As I understand this kind of exception occurs when we refer to a memory location that does not exist. But I know it is there since the iterator got its value and it was printed. Even so I used the second method just in case that I don't refer to non-existing memory location but still I'm getting the exception.
So how can I iterate over the elements print the result then erase it?
UPDATE1
following the suggestions below and the linked topic I changed my function into this:
void printMap(){
bool i = true;
for (auto it = MyMap.cbegin(), next_it = MyMap.cbegin(); it != MyMap.cend(); it = next_it)
{
cout << it->second;
next_it = it; ++next_it;
if (i) {
MyMap.erase(it);
}
}
}
I have also tried this https://stackoverflow.com/a/42820005/7631183 and this https://stackoverflow.com/a/42819986/7631183
The problem is still not solved and I'm getting the same error
UPDATE2
I run the same exact code on a different machine and it worked fine. I still don't know what is the reason so I would guess as suggested in the comments that std::map on the first had some problems.
P.S. The first machine was a mac and the second was a Linux

When you erase an element out of the map, you have invalidated the iterator you are using in the loop, thus causing error. The erase method returns an iterator for this reason.
for( auto it = MyMap.begin(); it != MyMap.end(); )
{
std::cout << it->second;
it = MyMap.erase(it);
}
This is indeed a duplicate question. You just don't understand it well enough to see that it is a duplicate. I hope the explanation of invalidating the iterator helps clear it up.

In C++11 and later, std:map::erase() returns an iterator to the element following the one being erased. Since you are using auto, you are using C++11 or later, so you can use the new iterator when erasing while looping through the std::map, eg:
void printMap(){
auto it = MyMap.cbegin();
while (it != MyMap.cend()) {
std::cout << it->second;
it = MyMap.erase(it);
}
}
Also, since you are using C++11, your first print function can be simplified using a range-for loop:
void printMap(){
for ( auto &it: MyMap ){
std::cout << it.second;
}
}

Related

referencing an object using auto iterator

unordered_map<char,int> letter;
for (auto it = letter.begin(); it != letter.end() ; it++) {
if (it->second) return false;
}
for (auto it : letter) {
if (it.second) return false;
}
Above, there are 2 iterator loops which I believe output the same thing. I can understand that the it in the first loop points to the object in the unordered_map, so the second variable must be referenced with ->. But I dont understand how the second loop can do .second. Can anyone explain how to 2nd loop works?
The second loop is a range-based for loop. It is not returning an iterator, but is instead returning a copy of the key-value pair (pair<char, int>), so it does not need to ues a -> operator to access the values.
Your range-based for would be equivalent to this, only less verbose, of course.
for (auto it = letter.begin(); it != letter.end() ; it++) {
auto kvp = *it;
if (kvp.second) return false;
}

Converting const auto & to iterator

A number of posts I've read lately claim for(const auto &it : vec) is the same as using the longer iterator syntax for(std::vector<Type*>::const_iterator it = vec.begin(); it != vec.end(); it++). But, I came upon this post that says they're not the same.
Currently, I'm trying to erase an element in a for loop, after it is used, and wondering if there is any way to convert const auto &it : nodes to std::vector<txml::XMLElement*>::iterator?
Code in question:
std::vector<txml2::XMLElement *> nodes;
//...
for (const auto &it : nodes)
{
//...
nodes.erase(it);
}
I pretty sure I could just rewrite std::vector<txml2::XMLElement*> as a const pointer, but would prefer not to since this code is just for debugging in the moment.
You should not be attempting to convert the range declaration in your range based for loop to an iterator and then deleting it whilst iterating. Even adjusting iterators while iterating is dangerous, and you should instead rely on algorithms.
You should use the Erase-remove idom.
You can use it with remove_if.
It would look something like:
nodes.erase( std::remove_if(nodes.begin(), nodes.end(), [](auto it){
//decide if the element should be deleted
return true || false;
}), nodes.end() );
Currently in the technical specifications, is erase_if.
This is a cleaner version of the same behaviour shown above:
std::erase_if(nodes,[](auto it){
//decide if the element should be deleted
return true || false;
});
You don't get an iterator but a reference to the element. Unless you want to do a std::find with it, it's pretty hard to get an iterator out of it.
Vectors are nice, so you could increase a counter per element and do nodes.begin() + counter to get the iterator, but it'd sort of defeat the point.
Also erasing the iterator in the for loop will result in you iterating after the end of the vector, you can test this code:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {0,1,2,3,4,5,6};
for (int x : v) {
cout << x << endl;
if (x == 2) {
v.erase(v.begin() + 2);
}
}
return 0;
}
If you want to use iterators, just do a loop with them, if in addition you want to erase one mid-loop you have to follow this answer:
for (auto it = res.begin() ; it != res.end(); ) {
const auto &value = *it;
if (condition) {
it = res.erase(it);
} else {
++it;
}
}
Note that you don't need to specify the whole type of the iterator, auto works just as well.

Vector not dereferencable

After having looked at the comments I looked through the code and found an error.
It seems after some tinkering I got faced with this error:
Debug error: vector iterator is not dereferencable.
I'm 100% certain that it is in the vector inside assingthreads.
This is the newly added code that spawns the error:
void historical::writeData(std::vector<std::vector<std::wstring>> in, const string& symbol) {
std::cout << "Sending data to database connector" << std::endl;
std::vector<std::vector<std::wstring>> temp;
while (!in.empty()) {
for (int i = 0; i < 5; i++) {
temp.push_back(in.back());
in.pop_back();
}
assignthreads(temp, symbol);
temp.clear();
}
}
void historical::assignthreads(std::vector<std::vector<std::wstring>> partVec, const string& symbol) {
int i = 0;
std::thread threads[5];
std::vector<std::vector<std::wstring>>::iterator it;
for (it = partVec.end();
it != partVec.begin();
it--) {
std::shared_ptr<database_con> sh_ptr(new database_con);
threads[i] = std::thread(&database_con::start, sh_ptr, *it, symbol);
partVec.pop_back();
i++;
}
for (auto& th : threads) th.join();
}
Your first time through the for-loop, it = partVec.end().
By definition you cannot dereference the end of a vector but you call:
threads[i] = std::thread(&database_con::start, sh_ptr, *it, symbol);
The for loop you intended probably used reverse iterators, rbegin and rend like this:
for(auto it = rbegin(partVec); it != rend(partVec); ++it)
A couple additional notes:
Pass your vector by reference: void assignthreads(std::vector<std::vector<std::wstring>>& partVec, const string& symbol)
You need to validate that threads is the same size as partVec. So either do: vector<thread> threads(size(partVec)) or after threads is defined do: assert(size(threads) == size(partVec))
At least one issue with the for loop in assignthreads is that you attempt to dereference the end() of the vector;
for (it = partVec.end(); it != partVec.begin(); it--) {
// ...
threads[i] = std::thread(&database_con::start, sh_ptr, *it, symbol);
// ^^^^
}
And on the first iteration of the loop this is undefined; your debugger is just telling you that.
If you want to "reverse" through the loop, use the reverse_iterator of the container (available via rbegin() and rend())
for (it = partVec.rbegin(); it != partVec.rend(); ++it)
Side note it is generally not advised to modify the container whilst iterating through it (via partVec.pop_back();). Since you don't seem to do anything with what is removed from the vector, it may just as well be better to iterate over the contents, and then call std::vector<>::clear() to remove all the contents from the vector after the loop.

Erasing a specific instance of an object from a vector

I have a class Circle whose instances I keep track of with these:
Circle *f1;
vector<Circle> list;
vector<Circle>::iterator it;
I have managed to create multiple Circles and got them to move around. How can I erase a specific instance of Circle? For example, if a certain circle hits a wall, then it should be erased. I've looked around at other questions and I even tried the code they gave out and no luck. Here's what I've got at the moment:
for (it = list.begin(); it != list.end(); ++it) {
it->x += 1;
if (it->x == ofGetWindowWidth()) {
list.erase(it);
}
}
I have gotten other statements to work with the if statement such as reversing the direction of their movement. list.erase(it); was a line of code I got from here and I don't understand why it crashes my program.
for (it = list.begin(); it != list.end(); /* nothing here */) {
it->x += 1;
if (it->x == ofGetWindowWidth()) {
it = list.erase(it);
} else {
++it;
}
}
The problem with your original code is that erasing an element invalidates the iterator to that element - the very same iterator you are trying to increment next. This exhibits undefined behavior.
list.erase invalidates iterators to the erased element. Therefore, after you erase the element pointed to by "it", "it" is invalidated and the ++it, which follows after the for loops body, can crash your program.
Rewriting your code to something similiar to the following should prevent your crash:
for(it=list.begin();it!=list.end(); ) {
//your code
if(it->x==ofGetWindowWidth())
it=list.erase(it);
else
++it;
}
The problem with the above code using erase() is that it invalidates the content of it when the element is being erase. You can use, e.g., this instead:
for (it = list.begin(); it != list.end(); ) {
it->x += 1;
if (it->x == ofGetWindowWidth()) {
list.erase(it++);
}
else {
++it;
}
}
The branch using erase() moves the kept iterator it off its current location before erase()ing the element. Only the temporary object return from it++ gets invalidated. Of course, for this loop to work, you can't unconditionally increment it, i.e., the non-erase()ing branch needs its own increment.
You could use erase with remove_if. This also works for removal of multiple elements. In your case it's
list.erase(std::remove_if(list.begin(), list.end(),
[](const Circle& c){return c.x == ofGetWindowWidth();},list.end()),
Example with integers:
#include <algorithm>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> str1 = {1,3,5,7};
str1.erase(std::remove_if(str1.begin(), str1.end(),
[](int x){return x<4 && x>2;}), str1.end());
for(auto i : str1) std::cout << i ;
}
prints 157

Iterator invalidation - does end() count as an iterator or not?

I ran into the following problem using std::multimap::equal_range() and insert().
According to both cplusplus.com and cppreference.com, std::multimap::insert does not invalidate any iterators, and yet the following code causes an infinite loop:
#include <iostream>
#include <map>
#include <string>
int main(int argc, char* argv[])
{
std::multimap<std::string,int> testMap;
testMap.insert(std::pair<std::string,int>("a", 1));
testMap.insert(std::pair<std::string,int>("a", 2));
testMap.insert(std::pair<std::string,int>("a", 3));
auto range = testMap.equal_range(std::string("a"));
for (auto it = range.first; it != range.second; ++it)
{
testMap.insert(std::pair<std::string,int>("b", it->second));
// this loop becomes infinite
}
// never gets here
for (auto it = testMap.begin(); it != testMap.end(); ++it)
{
std::cout << it->first << " - " << it->second << std::endl;
}
return 0;
}
The intent is to take all existing items in the multimap with a particular key ("a" in this case) and duplicate them under a second key ("b"). In practice, what happens is that the first loop never exits, because it never ends up matching range.second. After the third element in the map is processed, ++it leaves the iterator pointing at the first of the newly inserted items.
I've tried this with VS2012, Clang, and GCC and the same thing seems to happen in all compilers, so I assume it's "correct". Am I reading too much into the statement "No iterators or references are invalidated."? Does end() not count as an iterator in this case?
multimap::equal_range returns a pair whose second element in this case is an iterator to the past-the-end element ("which is the past-the-end value for the container" [container.requirements.general]/6).
I'll rewrite the code a bit to point something out:
auto iBeg = testMap.begin();
auto iEnd = testMap.end();
for(auto i = iBeg; i != iEnd; ++i)
{
testMap.insert( std::make_pair("b", i->second) );
}
Here, iEnd contains a past-the-end iterator. The call to multimap::insert doesn't invalidate this iterator; it stays a valid past-the-end iterator. Therefore the loop is equivalent to:
for(auto i = iBeg; i != testMap.end(); ++i)
Which is of course an infinite loop if you keep adding elements.
The end-iterator range.second is not invalidated.
The reason that the loop is infinite, is that each repetition of the loop body:
inserts a new element at the end of the map, thus increasing the distance between it and the end by one (so, after this insert, range no longer represents the equal_range for the key "a" because you have inserted a new key within the range it does represent, from the first "a" to the end of the container).
increments it, reducing the distance between it and the end by one.
Hence, it never reaches the end.
Here's how I might write the loop you want:
for (auto it = testMap.lower_bound("a"); it != testMap.end() && it->first == "a"; ++it)
{
testMap.insert(std::pair<std::string,int>("b", it->second));
}
A solution to make it work as expected (feel free to improve, it's a community wiki)
auto range = testMap.equal_range(std::string("a"));
if(range.first != range.second)
{
--range.second;
for (auto it = range.first; it != std::next(range.second); ++it)
{
testMap.insert(std::pair<std::string,int>("b", it->second));
}
}