SFML community forums
Help => Graphics => Topic started by: vro on July 31, 2011, 04:01:00 am
-
I am using the Collision example/code from the wiki.
My game runs fine 90% of the time, but every once in a while it crashes on a random collision between a projectile and an asteroid.
When I debug I get a seg fault, and it points to the code below. Specifically the line with the getSprite() calls (it's just a "getter" function in the projectile and asteroid classes that returns a sf::sprite object)
Am I doing something wrong with the vector iter? Like I said the game usually runs without crashing, but the seg fault has to do something with my vector of projectile objects, trying to reach something that's not there anymore.
for (vector<Projectile*>::iterator iterP = pProjectileVector->begin(); iterP != pProjectileVector->end(); iterP++)
{
for (vector<Asteroid*>::iterator iterA = pAsteroidVector->begin(); iterA != pAsteroidVector->end(); iterA++)
{
if (DetectCollision.BoundingBoxTest((*iterP)->getSprite(), (*iterA)->getSprite()))
{
delete *iterP;
pProjectileVector->erase(iterP);
delete *iterA;
pAsteroidVector->erase(iterA);
iterP--;
iterA--;
Asteroids.setAsteroidCount(Asteroids.getAsteroidCount()-1);
}
}
}
-
Iterators are invalid once you modify their container.
-
that's why I decrement the iterator when a projectile collides.
if I am doing it wrong then how is it done correctly?
lot's of people have suggested to use a vector for this sort of thing
-
Once you erase() something from a vector, all existing iterators of that vector become invalid.
That means:
if (DetectCollision.BoundingBoxTest((*iterP)->getSprite(), (*iterA)->getSprite()))
{
//...
pProjectileVector->erase(iterP); // iterP is no longer valid after this call
//...
iterP--; // this is meaningless. Next loop iteration, iterP will be bad/invalid
// same problem with iterA
iterP--; is a bad idea anyway. A general rule of thumb is that if you need to have something that "undoes" the for loop increment, then you probably shouldn't be using a for loop.
To get a valid iterator after an erase, use erase's return value.
This code should work (untested):
vector<Projectile*>::iterator iterP = pProjectileVector->begin();
while( iterP != pProjectileVector->end() )
{
vector<Asteroid*>::iterator iterA = pAsteroidVector->begin();
while( (iterA != pAsteroidVector->end()) && (iterP != pProjectileVector->end()) ) // have to check iterP in this loop, too
// since it could reach the end of the vector as well
{
if (DetectCollision.BoundingBoxTest((*iterP)->getSprite(), (*iterA)->getSprite()))
{
delete *iterP;
iterP = pProjectileVector->erase(iterP); // iterP =
delete *iterA;
iterA = pAsteroidVector->erase(iterA); // iterA =
Asteroids.setAsteroidCount(Asteroids.getAsteroidCount()-1);
}
else // no collision
{
++iterA;
}
}
if(iterP != pProjectileVector->end())
++iterP;
}
-
thank you disch, makes perfect sense now.
I read at cplusplusreference that an iterator wasn't valid once you erased, but I thought it just meant it didn't automatically adjust the iterator for you.
as you can see i'm still learning the stl :/
thanks again
-
By the way, you should take a look at the STL algorithms std::remove() and std::remove_if(). They are far more efficient than a for-loop with erase() calls, namely O(n) vs. O(n²).