Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Problems with vector erase() function

Credits go to J-Gamer and Slartibartfast for helping out!
This thread is locked; no one can reply to it. rss feed Print
Problems with vector erase() function
NZheadshot
Member #15,682
July 2014

Ok so I've got these 2 vectors, one containing Zombie objects, the other containing Bullet objects. I've got a function which checks for a collision between the 2, and then sets both objects' isDead value to true. Then, I run another loop that erases all the isDead objects from the vectors. There are 2 problems.
1. Bullets are never erased. If a bullet hits a zombie, it just continues on as though nothing happened.
2. Every time a bullet hits the last zombie on the screen (last zombie in vector) the program crashes.

Here's the function that checks for collision between each zombie and each bullet:

#SelectExpand
1void bulletZombieCollision() 2{ 3 static bool collision = TRUE; 4 5 int bleft; 6 int bright; 7 int btop; 8 int bbottom; 9 10 int zleft; 11 int zright; 12 int ztop; 13 int zbottom; 14 15 for (int i=0; i<zombie.size(); i++) 16 { 17 zleft = zombie[i].getLeft(); 18 zright = zombie[i].getRight(); 19 ztop = zombie[i].getTop(); 20 zbottom = zombie[i].getBottom(); 21 22 for (int j=0; j<bullet.size(); j++) 23 { 24 25 bleft = bullet[j].getLeft(); 26 bright = bullet[j].getRight(); 27 btop = bullet[j].getTop(); 28 bbottom = bullet[j].getBottom(); 29 30 if(bbottom < ztop || btop > zbottom || bright < zleft || bleft > zright) 31 collision = FALSE; 32 33 if (collision) 34 { 35 bullet[j].kill(); 36 zombie[i].kill(); 37 score++; 38 return; 39 } 40 41 collision = true; 42 } 43 } 44}

And here is the code in main() which erases dead objects:

for (std::vector<Bullet>::iterator i=bullet.begin(); i!=bullet.end(); i++)
    if (i->checkIfDead())
        bullet.erase(i);
         
for (std::vector<Zombie>::iterator i=zombie.begin(); i!=zombie.end(); i++;)
    if (i->checkIfDead())
        zombie.erase(i);

Sorry for just throwing up all this code, but I'm not sure what to do anymore. Thanks for any help :)

#00JMP00
Member #14,740
November 2012

I wonder if all the for-loops (edit:in main) run as expected, as there are no braces around the scope. How should the compiler know the start and the end.

Edit: This problem could also explain the crash. i should not be defined, as it is in the for scope or be some value, but there is no item to erase->out of boundary.

J-Gamer
Member #12,491
January 2011
avatar

You don't want to erase while iterating over the vector. This is because it erasing an element invalidates all iterators. You could try using std::remove_if in the following way:

#include <algorithm>

bullet.erase(std::remove_if(bullet.begin(),
                             bullet.end(),
                             [](const Bullet& b) {return b.checkIfDead();}),
               bullet.end());

This uses a lambda function, so if you aren't working in c++11(you can enable it with the compiler flag -std=c++11), you'll have to find another way.

@#00JMP00: for, while and if-statements have the first statement after them implicitly in scope. You don't need braces if you only have one inner statement(though it is recommended in most coding style guidelines).

" There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo
"If your body was a business, thought would be like micro-management and emotions would be like macro-management. If you primarily live your life with emotions, then you are prone to error on the details. If you over-think things all the time you tend to lose scope of priorities." - Mark Oates

Slartibartfast
Member #8,789
June 2007
avatar

The correct way of removing an item during iteration is: (assuming no C++11)

for (std::vector<Bullet>::iterator i=bullet.begin(); i!=bullet.end();)
{
    if (i->checkIfDead())
    {
        i = bullet.erase(i);
    }
    else
    {
        ++i;
    }
}

If you'd look at vector::erase's documentation you'll see it returns

Quote:

An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.

NZheadshot
Member #15,682
July 2014

Solved it. While I imagine that the answers here would have probably fixed the issue, I think this solution works just as well. For anyone who wants to know, here is the new code:

for (std::vector<Bullet>::iterator i=bullet.begin(); i!=bullet.end(); i++)
{
    if (i->checkIfDead())
        bullet.erase(i);
    if (bullet.empty())
        break;
}

J-Gamer
Member #12,491
January 2011
avatar

IIRC, that will skip the bullet after each dead bullet. It works since that bullet, if it was already dead, will be deleted the next frame, so you probably don't notice(and that only happens if two consecutive bullets hit in the same frame, which is unlikely).

" There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo
"If your body was a business, thought would be like micro-management and emotions would be like macro-management. If you primarily live your life with emotions, then you are prone to error on the details. If you over-think things all the time you tend to lose scope of priorities." - Mark Oates

pkrcel
Member #14,001
February 2012

I dare say the bullet.empy() is redundant.

Anyway you're skipping checks due to the i++ in the for loop expression, as others have noted.

It is unlikely that Google shares your distaste for capitalism. - Derezo
If one had the eternity of time, one would do things later. - Johan Halmén

NZheadshot
Member #15,682
July 2014

Alright so I went ahead and changed the code again. It now resembles what Slartibartfast mentioned, and its working swimmingly.

n.a n.a
Member #12,793
April 2011

I use the swap´n pop method instead of using erase.

Example:

for(int i=0;i < myVec.size();i++){
if(condition){//collision or what ever
std::swap(myVec[i],myVec.back());
myVec.pop_back()
}
}

It is faster since C11.;)

J-Gamer
Member #12,491
January 2011
avatar

n.a n.a said:

for(int i=0;i < myVec.size();i++){
if(condition){//collision or what ever
std::swap(myVec[i],myVec.back());
myVec.pop_back()
}
}

That code has some problems.
1: It will not check the last element if it is swapped into another position. When deleting an element, you put the last element in its place and on the next iteration, i is incremented, so you are checking the next element. This can be fixed by decrementing i.
2: the order of the elements is not preserved. This might or might not be a problem depending on your application.

" There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo
"If your body was a business, thought would be like micro-management and emotions would be like macro-management. If you primarily live your life with emotions, then you are prone to error on the details. If you over-think things all the time you tend to lose scope of priorities." - Mark Oates

adam32
Member #15,690
July 2014

You can erase from a vector while in a loop like this:

for (int i = 0; i != bullet.size(); i++)
{

    //update bullet here

    if (bullet[i]->checkIfDead())
    {
        bullet.erase(i);
        i--;
        continue;
    }
}

The continue isn't necessary if this if statement is at the end of the for loop, but if there's code after it the continue is recommended. Typically you put the erase code at the end of the for loop anyway.

#00JMP00
Member #14,740
November 2012

A whole lotta ado about nothing.

Why wouldn't someone do this:

#SelectExpand
1 2for (int i = bullet.size()-1; i >-1; i--) 3{ 4 5 //update bullet here 6 7 if (bullet[i]->checkIfDead()) 8 { 9 bullet.erase(i); 10 // i--; 11 continue; 12 } 13} 14 15Furthermore the whole thing could become obsolet by keeping the shot zombies and just recycle them. What about this?

NZheadshot
Member #15,682
July 2014

I wish it were that easy, but sadly, the erase() function requires an iterator. Your code would give and error because you tried send it an int

J-Gamer
Member #12,491
January 2011
avatar

You could use bullet.erase(bullet.begin()+i) if it requires an iterator.

" There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo
"If your body was a business, thought would be like micro-management and emotions would be like macro-management. If you primarily live your life with emotions, then you are prone to error on the details. If you over-think things all the time you tend to lose scope of priorities." - Mark Oates

Go to: