std::list - removing elements [duplicate] - c++

This question already has answers here:
Why does a push_back on an std::list change a reverse iterator initialized with rbegin?
(3 answers)
Closed 5 years ago.
I am currently using std::list in my application and trying get rid of problem with going out of range.
I really need to use pop_back in one object's method while iterating. I think it may change std::list::end in a some way. Is there any possibility to make it work properly? This code should look simillar to this:
Edit: I'm using GCC 6.1
Edit 2: If you have same problem related to reverse_iterator I recommend you to redesign application as far as it is possible and use casual iterator. It's more intuitive.
#include <list>
struct Object;
std::list<Object*> list;
struct Object
{
Object(bool state) : state(state) {}
bool state;
void method()
{
if(state) list.pop_back();
}
};
int main()
{
list.push_back(new Object(false));
list.push_back(new Object(true));
list.push_back(new Object(false));
for(auto it = list.rbegin(); it != list.rend(); ++it)
{
(*it) -> method();
}
return 0;
}

This should only be a problem if you are popping back the very last (first with your reverse iterators) element you are visiting right now. This is because you are incrementing the iterator which was invalidated by pop_back()
If this is the case, one solution would be to increment iterator first, store the result and than call the method().

The problem is that it is invalidated the moment you call pop_back() causing undefined behavior when you try to use it. You should store the next step before calling your method:
for(auto it = list.rbegin(); it != list.rend();)
{
auto itr = it++;
(*itr) -> method();
}

Related

stl list passed by reference to a function is not letting to be modified by the reference [duplicate]

Can I remove elements from std::list, when I'm iterating on it? For example so:
std::list<int> lst;
//....
for (std::list<int> itr = lst.begin(); itr != lst.end(); itr++)
{
if (*itr > 10)
lst.remove(*itr);
}
?
And why?
The correct code is the following:
for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); /*nothing*/)
{
if (*itr > 10)
itr = lst.erase(itr);
else
++itr;
}
When you delete an item from the list, you may invalidate the iterator (if it points to the item being deleted.) Therefore you need to delete using erase (which returns a valid iterator pointing to the next item).
Even better idea would be using std::remove_if:
bool greater_than_10(int x)
{
return x > 10;
}
lst.remove_if(greater_than_10);
If your compiler supports lambdas, you can put it even shorter:
lst.remove_if([](int x){ return x > 10; });
(I didn't test this code, as my compiler is not so new; the lambda function is thankfully stolen from #John Dibling's answer.)
Actually, erasing from list invalidates only the iterators pointing to the item being deleted. Beware however that other STL containers don't have this property.
So, in short: generally speaking, you should not delete the items from the list while iterating through it, because the deletion may invalidate the iterator (and the program will possibly crash). If you are however completely sure that the items which you delete are not the values referenced by any of the iterators which you use at the moment of deletion, you may delete.
Beware that for the other STL containers (e.g. vectors) the constraint is even more strict: deleting from the container invalidates not only iterators pointing to the deleted item, but possibly other iterators, too! So deleting from that containers while iterating through them is even more problematic.
No. The example code invalidates itr, causing undefined behavior. But this would work:
for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); )
{
if (*itr > 10)
itr = lst.erase(itr);
else
++itr;
}
No, you can't.
But you can (and should) use std::remove_if along with a functor that says "greater than 10`, like this:
#include <list>
#include <algorithm>
int main()
{
std::list<int> lst;
lst.push_back(1);
lst.push_back(12);
lst.push_back(1);
//....
lst.erase(std::remove_if(lst.begin(), lst.end(), std::bind2nd(std::greater<int>(), 10)), lst.end());
}
Another, more generic way to do this is to write your own custom functor. Here is a functor is_a_match that returns true if the value being checked is greater than 10. You can redefine operator() to return true to correspond to whatever it means in your case to "match":
#include <list>
#include <algorithm>
#include <functional>
struct is_a_match : public std::unary_function<int, bool>
{
is_a_match(int val) : val_(val) {};
bool operator()(int victim) const { return victim > val_; }
private:
int val_;
};
int main()
{
std::list<int> lst;
lst.push_back(1);
lst.push_back(12);
lst.push_back(1);
//....
lst.erase(std::remove_if(lst.begin(), lst.end(), is_a_match(10) ));
}
If you have the benefit of a C++0x conformant compiler, you can also use lambdas, which makes it possible to get rid of the functor and write more expressive code in many cases
#include <list>
#include <algorithm>
int main()
{
std::list<int> lst;
lst.push_back(1);
lst.push_back(12);
lst.push_back(1);
//....
lst.erase(std::remove_if(lst.begin(), lst.end(), [](int v) {return v > 10;}));
}
I think you can, but you have to reassign the iterator after the removal of the element, which can be done with erasemethod instead that remove.
Otherwise it will be unsafe and shouldn't be done.
See http://www.cppreference.com/wiki/iterator/start for a description of iterators.
A couple of notes:
You should be using the pre-increment operator (++itr) instead of the post-increment operator (itr++)
Invalidation depends on the exact implementation of the iterator and its associated collection.

"Vector Iterators incompatible" when deleting in a self contained vector loop

may I ask help to confirm if my issue comes from a Design problem or if there would be a possible clean solution to the following:
Entity.h
class CLEntity3D
{
public:
CLEntity3D();
virtual ~CLEntity3D();
virtual void update() = 0;
static std::vector<CLEntity3D*> vecEntity;
};
Entity.cpp
int CLEntity3D::nbrEntity = 0;
std::vector<CLEntity3D*> CLEntity3D::vecEntity;
CLEntity3D::CLEntity3D()
{
vecEntity.push_back(this);
}
CLEntity3D::~CLEntity3D()
{
vecEntity.erase((std::remove(vecEntity.begin(), vecEntity.end(), this)), vecEntity.end());
}
Various derived class are creating/deleting different Entities object through the program, this all works fine.
In a Scene class, I have the following methods:
void CLScene::Update()
{
for (auto& iter : CLEntity3D::vecEntity) {
iter->update();
}
}
void CLScene::ClearScene()
{
for (auto& iter : CLEntity3D::vecEntity) {
delete(iter); iter = nullptr;
}
CLEntity3D::vecEntity.clear();
}
Update is ok, the issue is with ClearScene(). I get a "Vector Iterators incompatible" debug assertion.
From my research, the common problem seems to be because the iterators are from different vectors, which I don't think is the issue here. I think the problem is when ClearScene() is called, every delete(iter) changes the size of vecEntity through the CLEntity3D destructor therefore invalidates the iterator in the ClearScene loop. Am I right?
My question would then be:
Is there a way to delete all CLEntity3D objects from CLScene with that design?
I guess I could have CLScene holding the vecEntity, which would eliminate the problem but this would mean that CLScene would have to manage all creation/deletion of entities, therefore not being as versatile...
PS: I know this example is not one to compile but since my question is more about concept... please advise if I shall provide otherwise.
The problem is, you can't remove anything from the underlying vector while inside a range based for loop.
The loop in your ClearScene method deletes CLEntity3D instances, which in it's destructor changes the same vector you used in your for loop.
A relatively easy fix would be to change your ClearScene to something like this:
void CLScene::ClearScene()
{
auto vectorCopy = CLEntity3D::vecEntity;
for (auto& iter : vectorCopy) {
delete iter;
}
}
This works because the loop operates on a copy, and the remove happens on the original.
Note that there is no need to clear the original vector after the loop, since the destructors ensure that the vector will be empty after deleting every item.
Or as suggested by a comment, you could avoid the copy by using a while loop:
while (!CLEntity3D::vecEntity.empty())
{
delete CLEntity3D::vecEntity.begin();
}

Circular iteration of std::list

In my application I need the ability to traverse a doubly linked list starting from any arbitrary member of the list and continuing past the end(), wrapping around to the begin() and continue until the traversal reaches where it started.
I decided to use std::list for the underlying data structure and wrote a circulate routine to achieve this. However it's showing certain unexpected behavior when it's wrapping around from end() to begin(). Here's my implementation
template <class Container, class BiDirIterator>
void circulate(Container container, BiDirIterator cursor,
std::function<void(BiDirIterator current)> processor)
{
BiDirIterator start = cursor;
do {
processor(cursor);
cursor++;
if (cursor == container.end()) {
cursor = container.begin(); // [A]
}
} while (cursor != start);
}
// ...
typedef int T;
typedef std::list<T> TList;
typedef TList::iterator TIter;
int count = 0;
TList l;
l.push_back(42);
circulate<TList, TIter>(
l, l.begin(),
[&](TIter cur) {
std::cout << *cur << std::endl;
count++;
}
);
The output is:
42
-842150451
When I step through the code I see that the line marked [A] is never reached. The cursor is never equal to container.end(). Surprisingly, invoking ++ on that cursor, takes it to container.begin() in next pass automatically. (I suppose that's specific to this STL implementation).
How can I fix this behavior?
The issue here is that you are taking Container by value. This causes a copy so the iterators returned by container.end() and container.begin() are not that same as the iterator passed to the function. Instead if you pass Container by reference then the code works correctly.
Live Example

std containers iterator invalidation during erase [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicates:
vector erase iterator
Problem with std::map::iterator after calling erase()
I am having a concern about a piece of my code that I have. I have components and an object which stores the components. The problem is during an update the component can tell to remove a component from a the object. But its called from another function.
void Object::update() { //using std::map here
for(ComponentMap::iterator i = components.begin(); i != components.end(); ++i) {
(*i).second->update();
}
}
void HealthComponent::update() {
if(health <= 0) object->removeComponent("AliveComponent"); //this is wrong logic. but its just an example :D
}
void Object::removeComponent(string component) {
ComponentMap::iterator i = components.find(component);
if(i == components.end()) return;
components.erase(i);
}
and suppose I have lots of components - Health, Alive, Graphics, Physics, Input etc.
I tried something like this (with some test components) and no errors during during update. But I am really concerned. Can it pop me an error in the future? If yes, how to fix it?
Thanks in advance,
Gasim
You cannot loop through your container and say ++i when i is potentially no longer valid (because you erased it). A typical erase loop goes like this:
for (it = x.begin(); it != x.end(); /* nothing here! */)
{
if (must_erase(*it))
{
x.erase(it++); // advance it while still valid, return previous and erase
}
else
{
++it;
}
}
Rewrite your code in this spirit.
To spell out your problem: In Object::update(), you call HealthComponent::update() which invalidates the iterator i, and then you call ++i, which is undefined behaviour.
In MSVC erase will return the next valid iterator however in GCC it returns void so the only portable way to deal with this issue is keeping the previous iterator, erasing the current element then incrementing the previous iterator for next iteration.
http://www.cplusplus.com/reference/stl/map/erase/
void Object::removeComponent(string component, ComponentMap::iterator& _prev )
{
ComponentMap::iterator i = components.find(component);
if(i == components.end())
return;
_prev = i;
--_prev;
components.erase(i);
++prev;
}

question about std::vector::end()

I recently finished fixing a bug in the following function, and the answer surprised me. I have the following function (written as it was before I found the bug):
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
vector<itemPtr>::iterator it; // itemPtr is a typedef for a std::tr1::shared_ptr<item::Item>
for(it=items.begin(); it!=items.end(); ++it)
{
if((*it)->getPosition() == pt)
{
item::Item item(**it);
items.erase(it);
vect.push_back(item);
}
}
}
This function finds all Item objects in the 'items' vector that has a certain position, removes them from 'items', and puts them in 'vect'. Later, a function named putItemsAt does the opposite, and adds items to 'items'. The first time through, getItemsAt works fine. After putItemsAt is called, though, the for loop in getItemsAt will run off the end of 'items'. 'it' will point at an invalid Item pointer, and getPosition() segfaults. On a hunch, I changed it!=items.end() to it<items.end(), and it worked. Can anyone tell me why? Looking around SO suggests it might involve erase invalidating the iterator, but it still doesn't make sense why it would work the first time through.
I'm also curious because I plan to change 'items' from a vector to a list, since list's erase is more efficient. I know I'd have to use != for a list, as it doesn't have a < operator. Would I run into the same problem using a list?
When you call erase(), that iterator becomes invalidated. Since that is your loop iterator, calling the '++' operator on it after invalidating it is undefined behavor. erase() returns a new valid iterator that points to the next item in the vector. You need to use that new iterator from that point onwards in your loop, ie:
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
vector<itemPtr>::iterator it = items.begin();
while( it != items.end() )
{
if( (*it)->getPosition() == pt )
{
item::Item item(**it);
it = items.erase(it);
vect.push_back(item);
}
else
++it;
}
}
You're invoking undefined behavior. All the iterators to a vector are invalidated by the fact that you called erase on that vector. It's perfectly valid for an implementation to do whatever it wants.
When you call items.erase(it);, it is now invalid. To conform to the standard, you must now assume that it is dead.
You invoke undefined behavior by using that invalid iterator in the next call to vect.push_back.
You invoke undefined behavior again by using it as the tracking variable of your for loop.
You can make your code valid by using std::remove_copy_if.
class ItemIsAtPoint : std::unary_function<bool, item::Item>
{
Point pt;
public:
ItemIsAtPoint(const Point& inPt) : pt(inPt) {}
bool operator()(const item::Item* input)
{
return input->GetPosition() == pt;
}
};
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
std::size_t oldSize = items.size();
std::remove_copy_if(items.begin(), items.end(), std::back_inserter(vect),
ItemIsAtPoint(pt));
items.resize(vect.size() - (items.size() - oldSize));
}
You can make this a lot prettier if you are using boost::bind, but this works.
I'll go with Remy Lebeau's explanation about iterator invalidation, and just add that you can make your code valid and asymptotically faster (linear time, instead of quadratic time) by using a std::list instead of a std::vector. (std::list deletions only invalidate the iterator that was deleted, and insertions don't invalidate any iterators.)
You can also predictibly identify iterator invalidation while debugging by activating your STL implementation's debug mode. On GCC, you do with with the compiler flag -D_GLIBCXX_DEBUG (see some caveats there).