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); }
);