2014-12-21 3 views
0

Этот пост не является дубликатом. Сначала прочитайте мой вопрос.Класс разрушения segfault

У меня, конечно, отсутствует что-то здесь. Я хотел бы, чтобы класс Entity смог уничтожить экземпляр самого себя, когда количество здоровья равно нулю.

virtual int takeDamage(int damage) { 
    health -= damage; 
    if(health <= 0) delete this; 
} 

Что не так с вышеуказанным кодом? Программа вызывается с segfault, когда вызывается последняя строка выше. До сих пор я пытался:

  • Создание пользовательского деструктора
  • Создание функции деструктора:

    виртуальная пустота уничтожить (Entity * е) { удаление е; }

  • Уничтожение объекта, где он был выделен

я удалил последнюю строку takeDamage(), а затем называется delete object из main.

if(tris[i]->getHealth() <=0) delete tris[i]; //called from the main function where a temporary collision detection occurs over all the entities in the program 

У меня есть проблема, и это сводится к следующему. Экземпляры всех Entity (ов) в моей программе (одной конкретной специализации, т. Е. Треугольники) хранятся в vector. Например, в приведенном выше коде используется этот вектор: vector<Triangle*> tris; (Triangle наследует Entity). Затем этот вектор повторяется и выполняются различные операции, такие как обнаружение столкновения, AI и т. Д. Теперь, когда один из объектов удаляется, в следующий раз, когда мы выполнили итерацию по всему вектору, мы перейдем к объекту, который имеет были удалены. Следующий вопрос: что бы я сделал, чтобы уменьшить этот вектор? ( - это хорошее место, чтобы отметить вопрос, возможно, отдельный вопрос!)

+0

Удаление объекта, который позднее может использовать другой код, приведет к ошибке. –

+1

'Следующий вопрос: что бы я сделал, чтобы сжать этот вектор?' Вы ищете «vector :: erase», случайно? –

+0

@IgorTandetnik Да! Это действительно то, что я ищу! Я нашел документацию об этом несколько мгновений назад, я обязательно посмотрю на нее и отправлю обратно, когда у меня будет решение! Спасибо! – PyroAVR

ответ

1

С учетом вашего описания, есть способ сделать это безопасно с использованием функций алгоритма. Функции алгоритма, которые вы, возможно, ищете, - 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; 
} 
+0

Ничего себе. Это использует множество функций, о которых я даже не подозревал, даже в стандартной библиотеке. Я провел некоторое исследование, и теперь я знаю, что они делают, даже если я не понимаю, как это сделать. Тем не менее, исследование также привело меня к выводу, что операция случайного доступа к вектору неэффективна по сравнению с «std :: list». Это правда? Из того, что я могу сказать, вышеупомянутый код все равно будет работать, поскольку стандартная библиотека довольно хорошо стандартизирована. Спасибо за вашу помощь! – PyroAVR

+0

Это не позволит мне редактировать вышеупомянутый комментарий ... Несмотря на это, я забыл упомянуть, что '~ Entity()' объявляется как виртуальный, виртуальный ~ Entity() {} ', а также по умолчанию. – PyroAVR

+0

@PyroAVR - Код будет работать в 'std :: list'. Функции алгоритма используют итераторы. Если тип итератора для контейнера соответствует алгоритму, алгоритм будет работать. – PaulMcKenzie

Смежные вопросы