С учетом вашего описания, есть способ сделать это безопасно с использованием функций алгоритма. Функции алгоритма, которые вы, возможно, ищете, - std::stable_partition
, for_each
и erase
.
Предположим, у вас есть «игровой цикл», и вы проверяете для столкновений во время цикла:
#include <algorithm>
#include <vector>
//...
std::vector<Entity*> allEntities;
//...
while (game_is_stlll_going())
{
auto it = allEntities.begin();
while (it != allEntitities.end())
{
int damage = 0;
// ... assume that damage has a value now.
//...
// call the takeDamage() function
it->takeDamage(damage);
++it;
}
// Now partition off the items that have no health
auto damaged = std::stable_partition(allEntities.begin(),
allEntities.end(), [](Entity* e) { return e->health > 0; });
// call "delete" on each item that has no health
std::for_each(damaged, allEntities.end(), [] (Entity *e) { delete e; });
// erase the non-health items from the vector.
allEntities.erase(damaged, allEntities.end());
// continue the game...
}
В основном то, что петля делает это, что мы проходим через все сущностей и вызовите takeDamage
для каждого из них , На этом этапе мы не удаляем никаких объектов. Когда цикл завершен, мы проверяем, какие предметы не имеют здоровья, отделяя поврежденные элементы с помощью функции алгоритма std::stable_partition
.
Почему stable_partition
, а не просто позвонить std::remove
или std::remove_if
, и перед удалением предметов позвоните по номеру delete
на удаленные предметы? Причина в том, что с remove/remove_if
вы не можете выполнить многоэтапный процесс удаления (вызовите delete
, а затем удалите его из вектора). Функции алгоритма remove/remove_if
предполагают то, что было перенесено в конец контейнера (то есть «удаленные» элементы) больше не нужны и, следовательно, не могут использоваться ни для чего, кроме окончательного вызова vector::erase
. Вызов delete
на удаленные элементы - это неопределенное поведение.
Чтобы решить эту проблему, вам необходимо сначала «отделить» плохие элементы, освободить память для каждого элемента и , затем удалить их. Нам нужно использовать алгоритм разбиения, поэтому у нас есть выбор: std::stable_partition
или std::partition
. Который из? Мы выбираем std::stable_partition
. Причина, по которой мы выбрали std::stable_partition
более std::partition
, заключается в сохранении относительного порядка предметов. Порядок элементов может быть важным для вашей реализации игры.
Теперь мы вызываем stable_partition
, чтобы разделить элементы на две части вектора объектов - хорошие элементы слева от раздела, плохие элементы справа от раздела. Функция лямбда используется для определения того, куда отправляется объект, путем тестирования значения health
. «Раздел» в этом случае является итератором, который возвращается stable_partition
.
Учитывая это, мы называем for_each
, где лямбда вызывает delete
по каждому пункту справа от раздела, а затем vector::erase()
, чтобы удалить все, справа от раздела. Затем мы снова зацикливаемся, переделывая весь этот процесс до окончания игры.
Еще одна вещь, которую вы заметите, - это безопасность вышеприведенного кода. Если все записи имеют какое-то здоровье, то вызовы stable_partition
, for_each
и erase
по существу не являются операционными. Поэтому нет необходимости явно проверять, по крайней мере, один предмет, не имеющий здоровья (но ничто не мешает вам сделать это, если вы считаете, что вам нужна эта микро-оптимизация).
А также убедитесь, что ваш базовый класс Entity
имеет virtual destructor
, иначе ваш код будет показывать неопределенное поведение при удалении.
Edit: takeDamage()
функция должна быть переписана неdelete this
вызова.
virtual int takeDamage(int damage) {
health -= damage;
return 0;
}
Удаление объекта, который позднее может использовать другой код, приведет к ошибке. –
'Следующий вопрос: что бы я сделал, чтобы сжать этот вектор?' Вы ищете «vector :: erase», случайно? –
@IgorTandetnik Да! Это действительно то, что я ищу! Я нашел документацию об этом несколько мгновений назад, я обязательно посмотрю на нее и отправлю обратно, когда у меня будет решение! Спасибо! – PyroAVR