I am trying to delete any element of this vector that collides with player. However when I try to remove the element from the vector the program crashes and I get the error; "vector iterator not incremental".
for (std::vector<Coin>::iterator i=CoinSet.begin(); i!=CoinSet.end(); i++)
{
if (i->PlayerClear(player.collider()) == true)
{
score++;
cout<<score<<endl;
CoinSet.erase(i);
}
}
This code works perfectly well until "CoinSet.erase(i)", I tried using "CoinSet.clear()" at various points, but to no avail. Any help on this would be great, thanks in advance!
This has been discussed to death. You mustn't operate on an invalid iterator. You want something like this:
for (auto it = CoinSet.begin(); it != CoinSet.end(); /* no increment here! */ )
{
if (/* ... */)
{
// ...
CoinSet.erase(it++);
}
else
{
++it;
}
}
I don't like putting ++-statements inside the argument. Therefore erase() returns an iterator that points to the next element, so one could replace the erase line with:
it = CoinSet.erase(it); // iterator is replaced with valid one
Related
I am trying to create a pacman game watching tutorials. So when the ghost is in weak state and the pacman collides with the ghost i am getting the error Vector iterator not incrementable. Can anyone please solve this. Thanks in advance.
for (Ghost*ghost : m_ghosts)
{
if (ghost->getCollisionBox().intersects(m_pcMan->getCollisionBox()))
{
if (ghost->isWeak())
{
m_ghosts.erase(std::find(m_ghosts.begin(), m_ghosts.end(),ghost));
m_score += 100;
}
else
m_pcMan->die();
}
}
NOTE: You used a range-based for-loop, but inside the for-loop, you still use std::find to find an iterator to the current element so that you delete, that's inefficient.
Your problem stems from the fact that, after you delete an element pointed to by an iterator, all iterators and references to that element and after becomes invalid. The range-based for-loop tries to eventually increment an invalid iterator which invokes Undefined Behavior. Luckily, your STL implementation has checked iterators, so you got some nice error message.
There are a variety of ways to go about this. Though you may want to consider your Data Structure... Game Devs have more advanced tricks for this sort of thing. Nonetheless, here is a trick, swap the current ghost with the last on the vector, then pop the last element.
auto iter = m_ghosts.begin();
auto End = m_ghosts.end();
while(iter != End)
{
if (iter->getCollisionBox().intersects(m_pcMan->getCollisionBox()))
{
if (iter->isWeak())
{
if(iter != std::prev(End)){ //We want to prevent `std::swap(x,x)`
std::iter_swap(iter, std::prev(End));
End = m_ghost.erase(std::prev(End));
}
else{
iter = End = m_ghost.erase(std::prev(End));
}
m_score += 100;
}
else{
++iter;
m_pcMan->die();
}
}
}
Thank you for the help guys. This worked for me :)
for (int i=0;i<m_ghosts.size();i++)
{
Ghost* ghost = m_ghosts[i];
if (ghost->getCollisionBox().intersects(m_pcMan->getCollisionBox()))
{
if (ghost->isWeak())
{
m_ghosts.erase(std::find(m_ghosts.begin(), m_ghosts.end(), ghost));
m_score += 100;
}
else
m_pcMan->die();
}
}
The following code throwing debug assertion map/iterator incremental error ..
void ClassA::Remove()
{
std::map<int, CVClassB*>::iterator it(m_p.begin());
while ( it != m_p.end() )
{
if (it->first >= 0)
{
m_p.erase(it);
it++;
}
}
}
Can you please let me know what is the error
std::map::erase invalidates the iterator on which it operates. So it is not safe to increment it afterwards. But erase() does return the next iterator for you:
it = m_p.erase(it);
Also, you only increment it inside the if, so unless all the keys are >=0, you will get stuck in an infinite loop. You probably wanted something like:
// delete all keys >= 0
if (it->first>=0) {
it = m_p.erase(it); // erase and increment
}
else {
++it; // just increment
}
Also, as Vlad's answer alludes to, who manages the lifetime of the CVClassB*? Do you need to delete it? Why use a pointer at all, you can probably store the value in the map directly. (Or use a smart pointer).
Write the loop like
while ( it != m_p.end() )
{
if (it->first >= 0)
{
it = m_p.erase(it);
}
else
{
++it;
}
}
Also it seems you should delete the object pointed to by the erased iterator.
For example
delete *it;
it = m_p.erase(it);
Your invalidating the iterator by removing inside the loop but in any case all that does is clear the map. Just call m_p.clear() and it will do exactly what you are trying to do. Although not sure what your trying to do is what you intended to do but that's another issue.
If you want to delete the objects pointed to then delete them then clear the map.
for(item : m_p)
delete item->second;
m_p.clear();
//done
I receive the message "Vector iterators incompatible". I tried to wrap my head around it, but nothing. I did it before. Same code, just not used in a class that receives "cWORLD* World". What am I doing wrong?
Thank you!
else if (Click[2] == true)
{
//go through objects and check collision
for (vector<cOBJECT*>::iterator it = World->ReturnWorldObjects().begin(); it != World->ReturnWorldObjects().end();)
{
//Check for collision and delete object
if (PointInRect(MouseX + offX, MouseY + offY, (*it)->getrect()) == true)
{
// delete object, delete slot, pick up next slot
delete *it;
it = World->ReturnWorldObjects().erase(it);
}
else
{ // no action, move to next
++it;
}
}//for
}//else if (Click[2] == true)
Looks like ReturnWorldObjects returns copy of vector, not reference. In this case, you are trying to compare iterators of different objects, that is not checked by standard, but can be checked by checked iterators (in this case, I think it's MSVC checked iterators).
Like #ForEveR already mentioned, you possibly return a copy of a vector in the function ReturnWorldObjects(). Without seeing the declaration of this method I can only assume it's something like vector<cOBJECT*> ReturnWorldObject();
You can come around this with 2 Solutions, I think:
1. Return a reference to the vector in your World Class
const vector<cOBJECT*>& ReturnWorldObjects()
{
return m_vecWorldObjects; // Your vector here
}
2. Get one copy of that function and use that in your code
...
vector<cOBJECT*> worldObjects = World->ReturnWorldObjects();
for (vector<cOBJECT*>::iterator it = worldObjects.begin(); it != worldObjects.end(); it++)
{
...
}
...
I'm trying to remove 'dead' bullets from my vector of bullets. Every frame, I'm calling the Bullet::update() function which looks like this:
void Bullet::update()
{
for(int i = 0; i != mAmmo.size(); i++)
{
if(mAmmo[i].sprite.getPosition().x > 700)
mAmmo[i].mAlive = false;
if(mAmmo[i].mAlive == false)
{
// I get a Debug Assertion Failed at runtime from this piece of code
mAmmo.erase(mAmmo.begin()+i);
}
if(mAmmo[i].mAlive == true)
{
mAmmo[i].sprite.move(mMovement);
}
}
}
Am I doing this completely incorrectly? This is the first time I've really used vectors more than just following through a tutorial. If I need to post any more code, just tell me. I've been working on this for the past few hours, so I'm a wee bit desperate to get this to work.
Thanks in advance!
You're easily walking into undefined behavior as soon as the ith element is the last element in your list. Use iterators, and pay special attention to the return value of erase(), as it automatically advances the iterator for you so your loop doesn't have to.
void Bullet::update()
{
for (auto it = mAmmo.begin(); it != mAmmo.end();)
{
if(it->sprite.getPosition().x > 700)
it->mAlive = false;
if (!it->mAlive)
{
// erase and get next iterator
it = mAmmo.erase(it);
}
else
{ // move and increment
it->sprite.move(mMovement);
++it;
}
}
}
I have a list of Star structs. These structs are in a std::list
I am double looping this list and compairing there locations to detect a collision. When A collision is found I will delete Star with the lowest mass. But how can I delete the Star when I am in the double Loop, and keep the loop going to check for more collisions?
It's worth mentioning that the second loop is a reverse loop.
Here is some code
void UniverseManager::CheckCollisions()
{
std::list<Star>::iterator iStar1;
std::list<Star>::reverse_iterator iStar2;
bool totalbreak = false;
for (iStar1 = mStars.begin(); iStar1 != mStars.end(); iStar1++)
{
for (iStar2 = mStars.rbegin(); iStar2 != mStars.rend(); iStar2++)
{
if (*iStar1 == *iStar2)
break;
Star &star1 = *iStar1;
Star &star2 = *iStar2;
if (CalculateDistance(star1.mLocation, star2.mLocation) < 10)
{
// collision
// get heaviest star
if (star1.mMass > star2.mMass)
{
star1.mMass += star2.mMass;
// I need to delete the star2 and keep looping;
}
else
{
star2.mMass += star1.mMass;
// I need to delete the star1 and keep looping;
}
}
}
}
}
You need to utilize the return value of the erase method like so.
iStar1 = mStars.erase(iStar1);
erase = true;
if (iStar1 == mStars.end())
break; //or handle the end condition
//continue to bottom of loop
if (!erase)
iStar1++; //you will need to move the incrementation of the iterator out of the loop declaration, because you need to make it not increment when an element is erased.
if you don't increment the iterator if an item is erased and check if you deleted the last element then you should be fine.
Since modifying the list invalidates the iterators (so that you cannot increment them), you have to keep safe the iterators before the list is changed.
In the most of the implementation std::list is a dual-linked list, hence a iteration like
for(auto i=list.begin(), ii; i!=list.end(); i=ii)
{
ii = i; ++ii; //ii now is next-of-i
// do stuff with i
// call list.erasee(i).
// i is now invalid, but ii is already the "next of i"
}
The safest way, is to create a list containing all the "collided", then iterate on the "collided" calling list.remove(*iterator_on_collided)
(but inefficient, since has O2 complexity)
You want to use the result of erase() to get the next iterator and advance the loop differently:
If you erase using the outer iterator you clearly can abondon checking this Star against others and break out of the inner loop. Only if the inner loop was complete you'd want to advance the outer iterator because otherwise it would be advanced by the erase().
If you erase using the inner loop you already advanced the iteration, otherwise, i.e. if no star was erased, you need to advance.
Sample code would look somethimg like this:
for (auto oit(s.begin()), end(s.end()); oit != end; )
{
auto iit(s.begin());
while (iit != end)
{
if (need_to_delete_outer)
{
oit = s.erase(oit);
break;
}
else if (need_to_delete_inner)
{
iit = s.erase(iit);
}
else
{
++iit;
}
}
if (iit == end)
{
++oit;
}
}