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;
}
Related
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();
}
Okay, I have a STL list of references I am iterating through. This function has three equivalent parts. The function takes a wstring as a parameter, and runs the appropriate if statement. I have reduced the code to one if statement to try and get it working.
So, I check to see what has been passed in as an argument. I then check to see if the ClassItem is a certain type of animal. If it is, I check if it is hungry, and erase it from the list. I am just trying to avoid seg faults right now, and cannot seem to do it.
list<ClassItem *>::iterator i = Items.begin();
while(i != Items.end())
{
if(!AnimalType.compare(L"tiger"))
{
if((*i)->IsAnimalType(L"tiger"))
{
if((*i)->IsHungry())
{
i = Items.erase(i);
}
}
else
{
++i;
}
}
// I have tried removing this
else
{
i++;
}
}
I was under the impression that the current iterator is invalidated when I call erase. So, if I erase an element, I return the next valid iterator. Where am I going wrong?
EDIT: Thank you for all the quick help. The problem has been fixed. I have made use phresnel's solution and it worked wonderfully.
You are better off by using std::list::remove_if with a suitable predicate. This avoids the manual loop entirely, reducing scope for errors, and helping to either remove or at least you localise the source of the problem, since you can rely on this idiom being correct as long as your predicate is.
bool badAnimal(ClassItem * item)
{
// return true if animal is to be removed
}
Items.remove_if(badAnimal);
I see no potential for a segfault here. Anyways:
There are (IMHO) two possible problems:
if(!AnimalType.compare(L"tiger"))
This looks fishy. What is AnimalType? Do you really expect the value of if(!AnimalType.compare(L"tiger")) to change during iteration, if AnimalType itself does not?
In any case, it looks like a read, therefore shouldn't write. It looks constant, therefore shouldn't change.
Then:
if((*i)->IsAnimalType(L"tiger"))
{
if((*i)->IsHungry())
{
i = Items.erase(i);
}
// NO ITERATION IN CASE OF NOT HUNGRY.
// ONCE TRAPPED HERE, YOU HAVE AN INFINITE LOOP,
// EXCEPT AnimalType.compare(L"tiger") DOES SOMETHING
// NON-SANE.
}
else
{
++i;
}
this should better be:
if((*i)->IsAnimalType(L"tiger") && (*i)->IsHungry())
{
i = Items.erase(i);
}
else
{
++i;
}
However, even better would be to use the standard algorithms for element removal.
you may want to add
continue;
after your erasion.
This question already has answers here:
How to delete an element from a vector while looping over it?
(6 answers)
Closed 9 years ago.
I use a vector of shared pointers to contain some game characters called customer.
typedef std::shared_ptr<Customer> customer;
std::vector<customer> customers;
customers.push_back(customer(new Customer()));
for(int i = 0; i < customers.size(); i++)
{
if(customers[i]->hasLeftScreen())
{
if(!customers[i]->itemRecieved())
outOfStocks++;
// Kill Character Here
}
}
I have used vectors to hold objects before so am used to calling erase on the vector and passing in the iterator. My question is there a way of deleting a the pointer from the vector in the above code snippet? I was hoping not to use an iterator here to simplify the code. I also need to delete the pointer because I was the customer to be removed from the game once it has left the screen.
Many thanks
Consider using an iterator, which frankly will be much easier to deal with. I'm not sure of your aversion to them, but see below:
std::vector<customer>::iterator it = customers.begin();
while (it != customers.end())
{
if(it->hasLeftScreen())
{
if(!it->itemRecieved())
outOfStocks++;
it = customers.erase(it);
continue;
}
++it;
}
This will remove the shared pointer instance from the vector. If the instance is the last reference to the shared pointer it will also release the associated memory of said Customer, firing its destructor, etc... (somewhat the point of using smart shared pointers in the first place, and props for using smart pointers, by the way).
You should always use iterators; it's a C++ idiom. This would change the code to...
for(auto i = customers.begin(); i != customers.end(); ++i)
{
if((*i)->hasLeftScreen())
{
if(!(*i)->itemRecieved())
outOfStocks++;
// Kill Character Here
}
}
Now, it is clear, we use the erase-remove idiom instead.
int outOfStocks = 0;
auto it = std::remove_if(customer.begin(), customers.end(), [&](Customer const& i) {
if(i->hasLeftScreen()) {
if(!i->itemRecieved()) {
outOfStocks++;
}
return true;
}
return false;
}
std::erase(it, customers.end());
You can also take advantage of "iterator arithmetic":
// Kill Character Here
customers.erase(customers.begin() + i);
... but that has a problem that customers.size() and the current index will get invalidated as the container will shrink.
Also, you don't need to explicitly delete the customer you're removing, because the smart pointer will take care of that.
I couldn't find an instance of how to do this, so I was hoping someone could help me out. I have a map defined in a class as follows:
std::map<std::string, TranslationFinished> translationEvents;
TranslationFinished is a boost::function. I have a method as part of my class that iterates through this map, calling each of the functions like so:
void BaseSprite::DispatchTranslationEvents()
{
for(auto it = translationEvents.begin(); it != translationEvents.end(); ++it)
{
it->second(this);
}
}
However it's possible for a function called by it->second(this); to remove an element from the translationEvents map (usually itself) using the following function:
bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
bool removed = false;
auto it = translationEvents.find(index);
if (it != translationEvents.end())
{
translationEvents.erase(it);
removed = true;
}
return removed;
}
doing this causes a debug assertion fail when the DispatchTranslationEvents() tries to increment the iterator. Is there a way to iterate through a map safely with the possibility that a function call during the iteration may remove an element from the map?
Thanks in advance
EDIT: Accidently C/Pd the wrong Remove Event code. Fixed now.
map::erase invalidates the iterator being deleted (obviously), but not the rest of the map.
This means that:
if you delete any element other than the current one, you're safe, and
if you delete the current element, you must first get the next iterator, so you can continue iterating from that (that's why the erase function for most containers return the next iterator). std::map's doesn't, so you have to do this manually)
Assuming you only ever delete the current element, then you could simply rewrite the loop like this:
for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
auto next = it;
++next; // get the next element
it->second(this); // process (and maybe delete) the current element
it = next; // skip to the next element
}
Otherwise (if the function may delete any element) it may get a bit more complicated.
Generally speaking it is frowned upon to modify the collection during iteration. Many collections invalidate the iterator when the collection is modified, including many of the containers in C# (I know you're in C++). You can create a vector of events you want removed during the iteration and then remove them afterwards.
After reading all other answers, I am at an advantage here... But here it goes.
However it's possible for a function called by it->second(this); to remove an element from the translationEvents map (usually itself)
If this is true, that is, a callback can remove any element from the container, you cannot possibly resolve this issue from the loop itself.
Deleting the current callback
In the simpler case where the callback can only remove itself, you can use different approaches:
// [1] Let the callback actually remove itself
for ( iterator it = next = m.begin(); it != m.end(); it = next ) {
++next;
it->second(this);
}
// [2] Have the callback tell us whether we should remove it
for ( iterator it = m.begin(); it != m.end(); ) {
if ( !it->second(this) ) { // false means "remove me"
m.erase( it++ );
} else {
++it;
}
}
Among these two options, I would clearly prefer [2], as you are decoupling the callback from the implementation of the handler. That is, the callback in [2] knows nothing at all about the container in which it is held. [1] has a higher coupling (the callback knows about the container) and is harder to reason about as the container is changed from multiple places in code. Some time later you might even look back at the code, think that it is a weird loop (not remembering that the callback removes itself) and refactor it into something more sensible as for ( auto it = m.begin(), end = m.end(); it != end; ++it ) it->second(this);
Deleting other callbacks
For the more complex problem of can remove any other callback, it all depends on the compromises that you can make. In the simple case, where it only removes other callbacks after the complete iteration, you can provide a separate member function that will keep the elements to remove, and then remove them all at once after the loop completes:
void removeElement( std::string const & name ) {
to_remove.push_back(name);
}
...
for ( iterator it = m.begin(); it != m.end(); ++it ) {
it->second( this ); // callback will possibly add the element to remove
}
// actually remove
for ( auto it = to_remove.begin(); it != to_begin.end(); ++it ) {
m.erase( *it );
}
If removal of the elements need to be immediate (i.e. they should not be called even in this iteration if they have not yet been called), then you can modify that approach by checking whether it was marked for deletion before executing the call. The mark can be done in two ways, the generic of which would be changing the value type in the container to be a pair<bool,T>, where the bool indicates whether it is alive or not. If, as in this case, the contained object can be changed you could just do that:
void removeElement( std::string const & name ) {
auto it = m.find( name ); // add error checking...
it->second = TranslationFinished(); // empty functor
}
...
for ( auto it = m.begin(); it != m.end(); ++it ) {
if ( !it->second.empty() )
it->second(this);
}
for ( auto it = m.begin(); it != m.end(); ) { // [3]
if ( it->second.empty() )
m.erase( it++ );
else
++it;
}
Note that since a callback can remove any element in the container, you cannot erase as you go, as the current callback could remove an already visited iterator. Then again, you might not care about leaving the empty functors for a while, so it might be ok just to ignore it and perform the erase as you go. Elements already visited that are marked for removal will be cleared in the next pass.
My solution is to first create a temporary container, and swap it with the original one. Then you can iterator through the temporary container and insert the ones you want to keep to the original container.
void BaseSprite::DispatchTranslationEvents()
{
typedef std::map<std::string, TranslationFinished> container_t;
container_t tempEvents;
tempEvents.swap(translationEvents);
for(auto it = tempEvents.begin(); it != tempEvents.end(); ++it)
{
if (true == it->second(this))
translationEvents.insert(it);
}
}
And the TranslationFinished functions should return true if it want to be keeped and return false to get removed.
bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
bool keep = false;
return keep;
}
There should be a way for you to erase a element during your iteration, maybe a little tricky.
for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
//remove the "erase" logic from second call
it->second(this);
//do erase and increase the iterator here, NOTE: ++ action is very important
translationEvents.erase(it++);
}
The iterator will be invalid once the element is removed, so you can not use that iterator to do increase action anymore after you remove it. However, remove an element will not affect other element in map implementation, IIRC. So suffix ++ will copy the iter first and increase the iterator right after that, then return the copy value, which means iterator is increased before erase action, this should be safe for you requirement.
You could defer the removal until the dispatch loop:
typedef boost::function< some stuff > TranslationFunc;
bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
bool removed = false;
auto it = translationEvents.find(index);
if (it != translationEvents.end())
{
it->second = TranslationFunc(); // a null function indicates invalid event for later
removed = true;
}
return removed;
}
protect against invoking an invalid event in the loop itself, and cleanup any "removed" events:
void BaseSprite::DispatchTranslationEvents()
{
for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
// here we invoke the event if it exists
if(!it->second.empty())
{
it->second(this);
}
// if the event reset itself in the map, then we can cleanup
if(it->second.empty())
{
translationEvents.erase(it++); // post increment saves hassles
}
else
{
++it;
}
}
}
one obvious caveat is if an event is iterated over, and then later on deleted, it will not get a chance to be iterated over again to be deleted during the current dispatch loop.
this means the actual deletion of that event will be deferred until the next time the dispatch loop is run.
The problem is ++it follows the possible erasure. Would this work for you?
void BaseSprite::DispatchTranslationEvents()
{
for(auto it = translationEvents.begin(), next = it;
it != translationEvents.end(); it = next)
{
next=it;
++next;
it->second(this);
}
}
How can i loop thru a stl::List and store the value of one of the objects for use later in the function?
Particle *closestParticle;
for(list<Particle>::iterator p1 = mParticles.begin(); p1 != mParticles.end(); ++p1 )
{
// Extra stuff removed
closestParticle = p1; // fails to compile (edit from comments)
}
Either
Particle *closestParticle;
for(list<Particle>::iterator it=mParticles.begin(); it!=mParticles.end(); ++it)
{
// Extra stuff removed
closestParticle = &*it;
}
or
list<Particle>::iterator closestParticle;
for(list<Particle>::iterator it=mParticles.begin(); it!=mParticles.end(); ++it )
{
// Extra stuff removed
closestParticle = it;
}
or
inline list<Particle>::iterator findClosestParticle(list<Particle>& pl)
{
for(list<Particle>::iterator it=pl.begin(); it!=pl.end(); ++it )
{
// Extra stuff removed
return it;
}
return pl.end();
}
or
template< typename It >
inline It findClosestParticle(It begin, It end)
{
while(begin != end )
{
// Extra stuff removed
return begin;
++begin;
}
return end;
}
These are sorted in increasing personal preference. :)
For a list, the only way to invalidate an iterator is to erase it. So I suspect you're calling list.erase(p1) at some point in the loop. You need to make a copy of the iterator, move p1 back one, and then erase the copy.
EDIT: Oh wait, did you mean it doesn't compile? If so, see #sbi's answer. But you really need to word your question in a good way. What is your compile error? Or does it fail at run-time? In this case, however, I believe you mean a compile error.