2015-09-25 26 views
6

Mam problem z segfault, którego nie mogę wymyślić. To od EntityManager dla małego silnika gry, nad którym pracuję. Mogę dodać Ship Entity, a statek może dodać 1 Bullet Entity, ale może to nastąpić, jeśli spróbuję dodać więcej niż 1 Bullet. Próbowałem to rozgryźć przez ostatni dzień. Poniżej znajduje się niewielki fragment rzeczywistego kodu.wektor usuwania unique_ptr?

#include <vector> 
#include <memory> 

struct EntityManager; 
struct Entity { 
    Entity(EntityManager* manager) : manager(manager) { } 
    virtual ~Entity() { } 
    virtual void update() = 0; 

    EntityManager* manager; 
}; 
struct EntityManager { 
    void update() { 
     for (auto& entity : entities) { 
      entity->update(); 
     } 
    } 
    void add(Entity* e) { 
     entities.emplace_back(e); 
    } 
    std::vector<std::unique_ptr<Entity>> entities; 
}; 
struct Bullet : public Entity { 
    Bullet(EntityManager* manager) : Entity(manager) { printf("Bullet ctor\n"); } 

    virtual void update() override { } 
}; 
struct Ship : public Entity { 
    Ship(EntityManager* manager) : Entity(manager) { } 

    virtual void update() override { 
     printf("Adding Bullet\n"); 
     manager->add(new Bullet(manager)); 
    } 
}; 
int main() { 
    EntityManager manager; 
    manager.add(new Ship(&manager)); 

    int loops{0}; 
    while (loops < 100) { 
     manager.update(); 
     loops++; 
     printf("Completed Loop #%d\n", loops); 
    } 
    return 0; 
} 

W rzeczywistym kodzie, wszystko jest w ich własnych plików .h/.cpp, a zajęcia zamiast elemencie, ale problem jest taki sam. Wyjście jest `Dodawanie Bullet Bullet konstruktor // // Zakończony pętla nr 1 // // Dodawanie Bullet Bullet konstruktor // sygnału: SIGSEGV (usterki Segmentacja)

segfault dzieje się w EntityManager::update() na linii entity->update();.

+1

'EntityManager' potrzebuje niestandardowych operacji przenoszenia, aby zaktualizować wskaźnik" menedżera "encji. – dyp

+1

Po zaktualizowaniu elementów pętla ta dodaje więcej elementów, które unieważniają iteratory. Nie możesz dodać do swojego wektora, gdy jesteś w trakcie przechodzenia przez niego. – Galik

+1

[Pisz gry, nie silniki] (http://geometrian.com/programming/tutorials/write-games-not-engines/) to dobra rzecz do przeczytania. To powiedziawszy, zamiast dodawać rzeczy natychmiast lub natychmiastowo niszczyć rzeczy, możesz opóźnić te operacje po pętli aktualizacji, poprzez zdarzenia lub cokolwiek, co uznasz za stosowne, aby nie unieważniało iteratorów. – aslg

Odpowiedz

13

Problem polega na tym, że ta pętla modyfikuje Vector:

for (auto& entity : entities) { 
     entity->update(); 
    } 

jesteś zajęty iteracja nim podczas modyfikowania wektor, aby dodać nowy element, który unieważnia iteratory wykorzystywane przemierzać pojemnik.

Zakres oparte for pętla jest rozszerzony przez kompilator:

auto begin = entities.begin(), end = entities.end(); 
for (; begin != end; ++begin) 
    begin->update(); 

Wywołanie begin->update() dodaje nowy element do wektora, który powoduje utratę iteratorami do pojemnika, tak ++begin jest niezdefiniowany zachowanie . Praktycznie rzecz biorąc, begin nie wskazuje już na wektor (ponieważ został ponownie przydzielony i zwolnił starą pamięć wskazaną przez begin), więc następne wywołanie begin->update() usuwa niepoprawny iterator, uzyskując dostęp do wolnej pamięci i blokowania.

Aby to zrobić bezpiecznie prawdopodobnie chcesz użyć indeksów nie iteratory:

for (size_t i = 0, size = entities.size(); i != size; ++i) 
    entities[i].update(); 

ten przechwytuje wielkości na początku pętli i tak tylko iteruje aż do ostatniego elementu, który występuje, gdy rozpoczyna się pętla, więc nowe elementy dodane do końca nie zostaną odwiedzone.

To nadal działa, gdy wektor jest modyfikowany, ponieważ nie przechowujesz iteratorów lub wskaźników do elementów, tylko indeks. Dopóki nie usuniesz elementów z wektora, indeks jest nadal ważny nawet po wstawieniu nowych elementów.

+0

Zmieniłem pętlę na stałą dla pętli, która uzyskuje dostęp przez iterator (auto index {0u}; index BFritz

+3

Nie, to nie jest bezpieczne. Nie jest to "poprawiona pętla for", ponieważ 'vec.size()' zmienia się, i może zapętlać się na zawsze, odwiedzając nowe jednostki podczas ich dodawania. Zdarza się, że powyższy przykład działa, ponieważ 'Bullet' nie modyfikuje wektora, ale odwiedzasz obie jednostki podczas pierwszej pętli aktualizacji. Jest dobry powód, dla którego napisałem poprawioną pętlę tak jak ja. Jak już powiedziałem: _ "Przechwytuje rozmiar na początku pętli, a więc tylko iteruje do ostatniego elementu, który istnieje po uruchomieniu pętli, więc nowe elementy dodane do końca nie zostaną odwiedzone." _ –

+0

Masz rację , po przeprowadzeniu testów okazało się, że nie działa tak, jak wspomniałem powyżej. Użyję wstępnie przechwyconych zmiennych, jak zasugerowałeś w swojej odpowiedzi. Szczerze myślę, że powinienem był wiedzieć, o co chodzi. Dzięki za pomoc! – BFritz