I'm currently trying to delete 2 elements from a vector if some condition is met. I can successfully remove a single element without the "vector iterator not dereferencable" error occuring, I know the problem is been caused by removing two elements at once which messes up with the Iterators but am unsure as to the correct way of removing more than one element at once.
vector<SomeObj*> objs;
vector<SomeObj*>::iterator it = objs.begin();
while (it != objs.end())
{
vector<SomeObj*>::iterator it2 = objs.begin();
bool deleted = 0;
while (it2 != objs.end())
{
if ((*it)->somecondition(**it2))
{
delete *it2;
*it2 = NULL;
it = objs.erase(it2);
delete *it;
*it = NULL;
it = objs.erase(it); //Will error here due to invalidating the iterator
deleted = 1;
break;
}
++it2;
}
if (!deleted)
++it;
}
The problem is that the first call to erase() might very well invalidate the other iterator. See this post for a quick summary of what gets invalidated when in various containers. I'd say the simplest solution is to first traverse the container and mark the entries to be erased but do not erase them, and then in a second scan just erase everything that was marked. For performance reasons in this second scan you should either use std::remove_if or use reverse iterator.
Working with nested iterators is tricky if you are mutating the container.
I've put together some sample code that does what you are wanting. What I'm doing is delaying the removal by setting the elements to be removed to nullptr and then removing those as we encounter them in the loops.
#include <iostream>
#include <vector>
class Example
{
public:
Example(int size) : size(size) {}
bool somecondition(const Example& other) const
{
return size == other.size;
}
int size;
};
int main()
{
std::vector<Example*> vec;
vec.push_back(new Example(1));
vec.push_back(new Example(2));
vec.push_back(new Example(3));
vec.push_back(new Example(2));
for (auto it1 = vec.begin(); it1 != vec.end();)
{
if (!*it1)
{
it1 = vec.erase(it1);
continue;
}
for (auto it2 = vec.begin(); it2 != vec.end(); ++it2)
{
if (!*it2)
{
vec.erase(it2);
// we need to start the outer loop again since we've invalidated its iterator
it1 = vec.begin();
break;
}
if (it1 != it2 && (*it1)->somecondition(**it2))
{
delete *it1;
*it1 = nullptr;
delete *it2;
*it2 = nullptr;
break;
}
}
++it1;
}
for (auto example : vec)
{
std::cout << example->size << std::endl;
}
return 0;
}
Related
I read in cppreference that erase does not affect iterators and references other than erased ones. But if I don't understand why following code does not work:
#include <iostream>
#include <list>
int main()
{
std::list<int> lst;
lst.push_back(5);
lst.push_back(10);
lst.push_back(20);
int i = 0;
for (auto it = lst.begin(); it != lst.end(); ++it) // FIRST LOOP
{
if (i == 1) { lst.erase(it); }
++i;
}
for (auto el : lst) // SECOND LOOP
{
std::cout << el << std::endl;
}
return 0;
}
First loop never stops which causes Process finished with exit code 139 (interrupted by signal 11: SIGSEGV). But I don't erase everything works fine.
So what is the problem?
UPD:
Tried to change
...
for (auto it = lst.begin(); it != lst.end(); ++it) // FIRST LOOP
...
to
...
auto end = lst.end();
for (auto it = lst.begin(); it != end; ++it) // FIRST LOOP
...
but it did not help
When you delete the element at it, you cannot increment it anymore. Therefore, you need to use this instead:
if (i == 1) { it = lst.erase(it); }
Also, you need to solve not to increment it in this case. So likely the increment would go inside the loop:
int i = 0;
for (auto it = lst.begin(); it != lst.end(); ) // FIRST LOOP
{
if (i == 1)
{
it = lst.erase(it);
}
else
{
++it;
}
++i;
}
I tried to follow the erase example for sample iterator over list but I can't make it work.
Here is the code so far:
for (list<list<string>>::iterator itr = listOfList.begin(), ; itr != listeOfListe.end(); itr++){
if (condition) {
for (list<string>::iterator it6 = itr->begin(); it6 != itr->end(); it6++)
{
itr.erase(*it6);
}
}
}
I get the following error:
class "std::_List_iterator<std::__cxx11::list<std::string, std::allocator<std::string>>>" has no member "erase"
Which indicates that it considers that itr is a list of list of string, why isn't itr simply a list of string since it iterates over listOfList?
You are trying to call erase() on an iterator itself, not on the list that the iterator refers to. That is why you are getting the compiler error. You need to dereference the iterator via operator* or operator-> to access the list to call erase() on.
Also, your inner loop is not accounting for the fact that list::erase() invalidates the specified iterator, so your use of it6++ is undefined behavior after calling erase(it6). You need to use the iterator that erase() returns in order to continue the loop correctly, eg: it6 = erase(it6).
Try something more like this instead:
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
if (condition) {
list<string>::iterator it6 = itr->begin();
while (it6 != itr->end()) {
it6 = itr->erase(it6);
}
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
if (condition) {
auto it6 = listOfStrings.begin();
while (it6 != listOfStrings.end()) {
it6 = listOfStrings.erase(it6);
}
}
}
Which simplifies to this:
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
if (condition) {
itr->clear();
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
if (condition) {
listOfStrings.clear();
}
}
Alternatively:
// pre C++11
if (condition) {
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
itr->clear();
}
}
// C++11 and later
if (condition) {
for (auto &listOfStrings : listOfList){
listOfStrings.clear();
}
}
I don't think that is what you are looking for, though. Your title says you want to "remove a string from a list", so you are probably looking for something more like this instead:
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
if (condition) {
itr->remove(string); // removes all matching strings
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
if (condition) {
listOfStrings.remove(string); // removes all matching strings
}
}
Alternatively:
// pre C++11
if (condition) {
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
itr->remove(string); // removes all matching strings
}
}
// C++11 or later
if (condition) {
for (auto &listOfStrings : listOfList){
listOfStrings.remove(string); // removes all matching strings
}
}
Or, maybe you are looking for something more like this instead?
// pre C++11
for (list<list<string>>::iterator itr = listOfList.begin(); itr != listOfList.end(); ++itr){
list<string>::iterator it6 = itr->begin();
while (it6 != itr->end()) {
if (condition(*it6)) {
it6 = itr->erase(it6);
} else {
++it6;
}
}
}
// C++11 and later
for (auto &listOfStrings : listOfList){
auto it6 = listOfStrings.begin();
while (it6 != itr->end()) {
if (condition(*it6)) {
it6 = listOfStrings.erase(it6);
} else {
++it6;
}
}
}
Which, in that latter case, simplifies to this in C++20:
for (auto &listOfStrings : listOfList){
std::erase_if(listOfStrings, [](string &s){ return condition(s); });
}
Please note that behavior of this program is undefined.
for (list<string>::iterator it6 = itr->begin(); it6 != itr->end(); it6++) {
itr->erase(it6); // !!
}
Code marked with // invalidates iterator it6. Incrementing it in the loop is now illegal, since only valid list iterators can be incremented.
To correct the snippet, use following:
for (list<string>::iterator it6 = itr->begin(); it6 != itr->end(); ) {
it6 = itr->erase(it6);
}
In this example, we use the fact that std::list::erase returns the iterator to the next element after the one erased.
However, this is only for illustration, assuming you would want to learn how to work with iterators. Better code would simply be
itr->clear();
I'm trying to remove elements that have the same key and value in a multimap. This is my code for now. After deleting the element, I get exception.
multimap<string, CStudent> m_StudentMap;
void removeDuplicates() {
for (auto it1 = m_StudentMap.begin(); it1 != --m_StudentMap.end(); it1++) {
for (auto it2 = next(it1, 1); it2 != m_StudentMap.end(); it2++) {
if (it1->first == it2->first) {
if (it1->second == it2->second) {
m_StudentMap.erase(it2);
}
}
}
}
}
You were nearly right, but the trick with erasing elements in maps while iterating is to capture the new iterator returned by erase. I've also generalised the function so it can be used on an argument rather than being limited to m_StudentMap, and stopped the inner loop as soon as the keys diverge.
template <typename K, typename V>
void removeDuplicates(std::multimap<K, V>& mmap)
{
if (mmap.size() < 2) return;
for (auto it = mmap.begin(); it != prev(mmap.end()); ++it)
for (auto it2 = next(it); it2 != mmap.end() && it2->first == it->first; )
if (it->second == it2->second)
it2 = mmap.erase(it2);
else
++it2;
}
You can see it run / fork it etc. here.
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
My STL is a bit rusty, so forgive me for asking a possibly trivial question. Consider the following piece of code:
map<int,int> m;
...
for (auto itr = m.begin(); itr != m.end(); ++itr) {
if (itr->second == 0) {
m.erase(itr);
}
}
The question is: Is it safe to erase elements while looping over the map?
Yes, but not the way you do it. You're invalidating itr when you erase, then incrementing the invalid iterator.
auto itr = m.begin();
while (itr != m.end()) {
if (itr->first == 0) {
m.erase(itr++);
} else {
++itr;
}
}
I think that you shouldn't use removed iterator at all - in case of lists this causes serious problems, shouldn't be different for maps.
EDIT by Matthieu M: this code is well-formed in C++0x and allowed as an extension by MSVC.
map<int,int> m;
...
auto itr = m.begin();
while (itr != m.end())
{
if (itr->second == 0) {
itr = m.erase(itr);
}
else
{
itr++;
}
}
For the example given, It would actually be easier to use the erase overload that takes a key as an argument. This function erases all elements in the map with the given key (for a map, this is always either zero or one element)
map<int,int> m;
// ...
m.erase(0); // erase all elements with key equivalent to 0