[SOLVED]: Applying proper list iteration procedure fixed problem. (Shown below)
I currently have a program in which elements of a list are iterated through and erased if they meet certain conditions. Due to the nature of the program, this can be visually seen.
Objects on screen that are being iterated through sometimes flicker on and off. This usually happens when objects around them are destroyed (i.e. erased in the code). At first I thought it was screen flickering, but now I realize that I think my iteration functions' logic may be causing the problem.
Here are the two functions. The first detects bullet collisions with blocks. If a bullet hits a block, the block is destroyed.
// Edit: WRONG WAY TO ITERATE THROUGH LIST
void DetectBulletCollisions()
{
std::list<Bullet>::iterator bullet = game::player_bullets.begin();
for ( ; bullet != game::player_bullets.end(); ++bullet)
{
if (bullet->IsOnScreen())
{
bullet->DetectBlockCollision(game::blocks);
}
else // Remove bullet from list
{
bullet = --game::player_bullets.erase(bullet);
}
}
}
This function moves the blocks that are flickering.
// Edit: RIGHT WAY TO ITERATE THROUGH LIST
void MoveBlocks(const int delta_ticks)
{
// Blocks on screen
std::list<Block>::iterator block = game::blocks.begin();
while (block != game::blocks.end()) // Loop through blocks
{
block->Show(); // Show block
if (!block->IsDestroyed()) // If block hasn't been destroyed
{
block->Move(delta_ticks); // Move block
++block; // Increment iterator
}
else // Block has been destroyed, remove it from list.
{
block = game::blocks.erase(block);
}
}
}
Is there something wrong with the logic of these loops? Notably the second one? It seems that when a block is destroyed, others around it flicker on and off (it isn't consistent, but that may just be frame rate). I'm not sure if the list rearranging the elements after each erasure would be a problem or not. The blocks each have coordinates, so it doesn't matter where they are in the list.
If any more information is needed I'd be happy to give it. I'm just wondering if my logic is wrong in the writing of these loops, or if I should take a different approach. I chose lists because they are the most efficient STL container for removing elements.
In both loops, when you erase an element, you assign the return value of erase to the loop iterator. According to cplusplus.com, list::erase returns the element after the erased element. So that code will always skip a bullet or a block when an erase happens if I'm not mistaken. Could that have anything to do with it?
Couldn't you use the double-buffering technique where you work on a background buffer for all these operations and once it is done you swap it out with the current front one so all the changes are done at once which would remove these flickers while you go through the list.
Your solution is still wrong. What if you erase the first item? You are not allowed to decrement the iterator to the beginning of a container.
A typical "erase while iterating" loop looks like this:
void DetectBulletCollisions()
{
std::list<Bullet>::iterator bullet = game::player_bullets.begin();
while (bullet != game::player_bullets.end()) //NB! No incrementing here
{
if (bullet->IsOnScreen())
{
bullet->DetectBlockCollision(game::blocks);
++bullet; //Only increment if not erased
}
else // Remove bullet from list
{
bullet = game::player_bullets.erase(bullet); //iterator "incremented" by erase
}
}
}
Related
I have a very weird problem and I apologize in advance if what I am about to say sounds silly.
I am developing a simple 3D game, and I am using c++, OpenGL 4.1, and Xcode 9.2 on MacBook Pro. I am a beginner, and I am struggling with vectors of objects. In particular, I managed to create several instances of a given object and display all of them in my scene. I am now trying to handle the deletion of one object at a time, say the first for simplicity. Therefore, I simply apply the erase() function to the vector, but, even though only the first element in the vector is actually erased, the last one also disappears from the scene.
I checked that the iterator was scanning the updated list correctly (although there was no need since the deletion is outside the for loop). I also paused the program and looked into the memory to see if the last object was still present, and it was. I thought the last object was simply overlapping another one, but was not because its model transformation matrix was different from the others.
As a last test (I am desperate now), I have written a portion of code that re-push_back()s all the elements when none is left after repeatedly use erase(). The problem persists.
What could I be doing wrong?
Here is part of the code (imagine that the vector "blocks" has already 6 elements in the beginning):
if (fire){ // mouse button pressed
fire = false;
num_of_blocks--;
blocks.erase(blocks.begin()); //delete first element, but last disappears!!
if (num_of_blocks==0){
first = true;
num_of_blocks = 6;
}
}
if (first){
first = false;
Vector3f pos = Vector3f(0,1,-3);
for (int i=0; i<num_of_blocks;i++){
pos.z() -= 2;
Parallelepiped newBlock = Parallelepiped(_textureWall.GetID(),2.0f,0.5f,1.2f);
blocks.push_back(newBlock);
blocks[i].setPosition(pos);
}
}
for (vector<Parallelepiped>::iterator it = blocks.begin(); it !=blocks.end(); it++){
drawObject(&shaders[0], &(*it));
}
Thank you in advance.
I have been struggling to put a vector object into a project im doing
I have read what little i could find about doing this and decided to give it a go.
std::vector<BrickFalling> fell;
BrickFalling *f1;
I created the vector. This next piece works fine until i get to the erase
section.
if(brickFall == true){
f1 = new BrickFalling;
f1->getBrickXY(brickfallx,brickfally);
fell.push_back(*f1);
brickFall = false;
}
// Now setup an iterator loop through the vector
vector<BrickFalling>::iterator it;
for( it = fell.begin(); it != fell.end(); ++it ) {
// For each BrickFalling, print out their info
it->printBrickFallingInfo(brick,window,deadBrick);
//This is the part im doing wrong /////
if(deadBrick == true)// if dead brick erase
{
BrickFalling[it].erase;//not sure what im supposed to be doing here
deadBrick = false;
}
}
You can totally avoid the issue by using std::remove_if along with vector::erase.
auto it =
std::remove_if(fell.begin(), fell.end(), [&](BrickFalling& b)
{ bool deadBrick = false;
b.printBrickFallingInfo(brick,window,deadBrick);
return deadBrick; });
fell.erase(it, fell.end());
This avoids the hand-writing of the loop.
In general, you should strive to write erasure loops for sequence containers in this fashion. The reason is that it is very easy to get into the "invalid iterator" scenario when writing the loop yourself, i.e. not remembering to reseat your looping iterator each time an erase is done.
The only issue with your code which I do not know about is the printBrickFallingInfo function. If it throws an exception, you may introduce a bug during the erasure process. In that case, you may want to protect the call with a try/catch block to ensure you don't leave the function block too early.
Edit:
As the comment stated, your print... function could be doing too much work just to determine if a brick is falling. If you really are attempting to print stuff and do even more things that may cause some sort of side-effect, another approach similar in nature would be to use std::stable_partition.
With std::stable_partition you can "put on hold" the erasure and just move the elements to be erased at one position in the container (either at the beginning or at the end) all without invalidating those items. That's the main difference -- with std::stable_partition, all you would be doing is move the items to be processed, but the items after movement are still valid. Not so with std::remove and std::remove_if -- moved items are just invalid and any attempt to use those items as if they are still valid is undefined behavior.
auto it =
std::stable_partition(fell.begin(), fell.end(), [&](BrickFalling& b)
{ bool deadBrick = false;
b.printBrickFallingInfo(brick,window,deadBrick);
return deadBrick; });
// if you need to do something with the moved items besides
// erasing them, you can do so. The moved items start from
// fell.begin() up to the iterator it.
//...
//...
// Now we erase the items since we're done with them
fell.erase(fell.begin(), it);
The difference here is that the items we will eventually erase will lie to the left of the partitioning iterator it, so our erase() call will remove the items starting from the beginning. In addition to that, the items are still perfectly valid entries, so you can work with them in any way you wish before you finally erase them.
The other answer detailing the use of remove_if should be used whenever possible. If, however, your situations does not allow you to write your code using remove_if, which can happen in more complicated situations, you can use the following:
You can use vector::erase with an iterator to remove the element at that spot. The iterator used is then invalidated. erase returns a new iterator that points to the next element, so you can use that iterator to continue.
What you end up with is a loop like:
for( it = fell.begin(); it != fell.end(); /* iterator updated in loop */ )
{
if (shouldDelete)
it = fell.erase(it);
else
++it;
}
Consider the following incomplete snippet:
for (std::list<CollidableNode*>::iterator it = m_Enemies.begin(); it != m_Enemies.end();it++)
{
//for current position+1 to end loop
for (std::list<CollidableNode*>::iterator jt = it+1; jt != m_Enemies.end();jt++)
{
//do stuff
}
}
This code produces obvious errors, but illustrates what I'm trying to do, which is: in the nested loop, set the start point of the loop at the current position in the list, plus one position, so that no duplicate checks are carried out.
Considerations are that the list is highly dynamic in size, with the list being checked for items to remove every update, and new items being added often, so that removals will be faster than a vector.
Is it possible to offset the iterator to a desired position, and if so, how do I go about doing that?
Thanks in advance
Probably std::next is what you need.
One of the most frequent errors that occur in my code is that STL containers are modified during a loop.
Elements are removed or added during a loop execution so I usually run into out of bounds exceptions.
My for loops usually looks like this:
for (auto& Item : Items) { // Will not work when Items container is modified
//... loop logic
}
When multiple items can be removed, I use this monstrosity:
for (int Index=Items.size()-1;Index<=0;Index--) {
if (Index<Items.size()) { //Because multiple items can be removed in a single loop
//... loop logic
}
}
This looks bad and it makes me feel bad using that second option. The reason multiple items can be removed is due to events, where a single event can remove any number of elements.
Here is some pseudo code to illustrate when this occurs:
// for each button in vector<button> {
// process button events
// event adds more buttons to vector<button>
// *ERROR* vector<button> is modified during loop.
// }
In another example, imagine a vector with following items:
// 0 1 2 3 4 5 6 7 8 9
We start our loop at 0 and go element by element. At 4, I want to remove elements 1,4 and 9 so we can't use a normal loop here.
Use std::remove_if with a predicate that decide if a button needs to be removed:
bool needsRemoved(const Button& button);
vec.erase(std::remove_if(vec.begin(), vec.end(), &needsRemoved), vec.end());
EDIT: For your last example, the quadratic (i.e. bad for performance) algorithm is:
std::vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
auto end = vec.end();
for (auto it = vec.begin(); it < end; ++it)
{
std::set<int> bad = {1, 4, 9};
end = std::remove_if
(vec.begin(), end,
[bad](int x) { return (bad.find(x) != bad.end()); });
}
vec.erase(end, vec.end());
You will probably be better off using a container with fast lookup though (like a set, or a map).
There are pretty much two ways to do this reliably:
Iterate over a copy of the original container and manipulate the original. This may not be feasible unless your container stores pointers, not the actual elements directly.
Don't allow direct manipulation of the container, but instead mark the to-be-deleted elements somehow and sweep them after iterating. You can also support adding new elements by inserting them into a separate temporary container and appending to the original after the loop is done - you can also do this with the removed elements, obviating the need to store a "removed" flag in the elements themselves. This can of course be abstracted out with suitable add and remove functions.
Edit: The removal part of solution #2 can be nicely done with the erase-remove idiom shown by rectummelancolique.
Since there are buttons (and I hope there are not too many) you might want to add a flag to each button, which tells, if it has been processed completely or something like that. Then you look for the first item in the array which has not been processed and process it. You repeat this until all items have been processed.
for (;;) // breaks, when all items have been processed.
{
auto it = std::find( std::begin(Items), std::end(Items),
[](const Item & item){ return item.hasBeenProcessed(); }
if ( it == std::end(Items) )
break;
process( *it );
}
This should be safe. Note that this can have quadratic time complexity with respect to the number of items. As I said, there will hopefully not be too many items. If this is an issue, you might want to optimize this loop a little, for example starting the search where you left the last time. But do this only when it becomes an issue.
Since you're talking about buttons and button events: the
simplest solution is simply to reset the loop to the start when
you process an event:
for ( auto current = items.begin(); current != items.end(); ++ current ) {
if ( current->hasEventWhichNeedsProcessing() ) {
current->processEvent(); // possibly invalidates current
current = items.begin(); // revalidates current
}
}
If we're talking about button events (which occur due to
a human user action), this should be safe, since you should
normally be able to process all of the events before a new event
occurs. (For very rapidly occuring events, you may never reach
the final entry.)
I'm still not sure that it's the best solution, however.
Regardless of how you iterator, it means that you may treat
events in a different order than they arrive. A better solution
would be to push the events themselves onto a list, and then
process this list in order (as a queue).
The following C++ code fills a vector with a number of objects and then removes some of these objects, but it looks like it deletes the wrong ones:
vector<Photon> photons;
photons = source->emitPhotons(); // fills vector with 300 Photon objects
for (int i=0; i<photons.size(); i++) {
bool useless = false;
// process photon, set useless to true for some
// remove useless photons
if (useless) {
photons.erase(photons.begin()+i);
}
}
Am I doing this correctly? I'm thinking the line photons.erase(photons.begin()+i); might be the problem?
Definietly the wrong way of doing it, you never adjust i down as you delete..
Work with iterators, and this problem goes away!
e.g.
for(auto it = photons.begin(); it != photons.end();)
{
if (useless)
it = photons.erase(it);
else
++it;
}
There are other ways using algorithms (such as remove_if and erase etc.), but above is clearest...
the elegant way would be:
std::vector<Photon> photons = source->emitPhotons();
photons.erase(
std::remove_if(photons.begin(), photons.end(), isUseless),
photons.end());
and:
bool isUseless(const Photon& photon) { /* whatever */ }
The proper version will look like:
for (vector<Photon>::iterator i=photons.begin(); i!=photons.end(); /*note, how the advance of i is made below*/) {
bool useless = false;
// process photon, set useless to true for some
// remove useless photons
if (useless) {
i = photons.erase(i);
} else {
++i;
}
}
You should work with stl::list in this case. Quoting the STL docs:
Lists have the important property that insertion and splicing do not invalidate iterators to list elements, and that even removal invalidates only the iterators that point to the elements that are removed.
So this would go along the lines of:
std::list<Photon> photons;
photons = source->emitPhotons();
std::list<Photon>::iterator i;
for(i=photons.begin();i!=photons.end();++i)
{
bool useless=false;
if(useless)
photons.erase(i);
}
Erasing elements in the middle of a vector is very inefficient ... the rest of the elements need to be "shifted" back one slot in order to fill-in the "empty" slot in the vector created by the call to erase. If you need to erase elements in the middle of a list-type data-structure without incurring such a penalty, and you don't need O(1) random access time (i.e., you're just trying to store your elements in a list that you'll copy or use somewhere else later, and you always iterate through the list rather than randomly accessing it), you should look into std::list which uses an underlying linked-list for its implementation, giving it O(1) complexity for modifications to the list like insert/delete.