The lecturer is correct.
Your loop unconditionally increments j
on every iteration regardless of whether erase()
is called. So, whenever you do call erase()
, the indexes of the remaining elements decrement, but you increment j
anyway, skipping the next element that followed the "erased" element.
One solution is to increment j
only on iterations that do not call erase()
, eg:
std::vector<std::unique_ptr<Zombie>> zombies;
...
for(size_t j = 0; j < zombies.size(); )
{
auto &zombie = zombies[j];
if (bullet intersects zombie)
{
zombies.erase(zombies.begin()+j);
}
else
{
++j;
}
}
Or, using iterators instead of indexes:
std::vector<std::unique_ptr<Zombie>> zombies;
...
for(auto iter = zombies.begin(); iter != zombies.end(); )
{
auto &zombie = *iter;
if (bullet intersects zombie)
{
iter = zombies.erase(iter);
}
else
{
++iter;
}
}
An alternative solution is to use the remove-erase idiom instead, eg:
std::vector<std::unique_ptr<Zombie>> zombies;
...
zombies.erase(
std::remove_if(zombies.begin(), zombies.end(),
[&](std::unique_ptr<Zombie> &zombie) { return (bullet intersects zombie); }
),
zombies.end()
);
std::remove_if()
will first "remove" all of the matching elements by moving them to the end of the vector
, returning an iterator to the first "removed" element. And then passing that range of "removed" elements to std::vector::erase()
will physically remove them from the vector
.
Alternatively, in C++20 and later, you can use std::erase_if()
instead, which makes the remove_if()
and erase()
calls for you, eg:
std::vector<std::unique_ptr<Zombie>> zombies;
...
std::erase_if(zombies,
[&](auto &zombie){ return (bullet intersects zombie); }
);