2015-09-25 50 views
6

Ho un problema con un segfault che non riesco a capire. È da un EntityManager per un piccolo motore di gioco su cui sto lavorando. Posso aggiungere Ship Entity e la Nave può aggiungere 1 Bullet Entity, ma segfaults se provo ad aggiungere più di 1 Bullet. Ho cercato di capire questo per il giorno passato ora. Di seguito è riportato un breve estratto dal codice reale.vettore dell'eliminazione 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; 
} 

Nel codice vero e proprio, tutto è nelle proprie file .h/cpp, e classi invece di struct, ma il problema è lo stesso. L'uscita è `Aggiungendo pallottola // pallottola ctor // completate Loop # 1 // Aggiunta pallottola // pallottola ctor // segnale: SIGSEGV (Segmentation fault)

Il segfault avviene nel EntityManager::update() sulla linea entity->update();.

+1

'EntityManager' richiede operazioni di spostamento personalizzate per aggiornare il puntatore' manager' delle entità. – dyp

+1

Quando aggiorni le entità, quel loop aggiunge più entità che invalida i tuoi iteratori. Non puoi aggiungere al tuo vettore mentre sei nel mezzo del looping. – Galik

+1

[Scrivi giochi non motori] (http://geometrian.com/programming/tutorials/write-games-not-engines/) è una buona cosa da leggere. Detto questo, invece di aggiungere immediatamente le cose o distruggere le cose immediatamente, è possibile ritardare tali operazioni dopo il ciclo di aggiornamento, attraverso eventi o qualsiasi cosa si ritenga appropriata, in modo da non invalidare gli iteratori. – aslg

risposta

13

Il problema è che questo ciclo modifica il vettore:

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

Sei iterazione occupato attraverso di essa quando si modifica il vettore per aggiungere un nuovo elemento, che invalida i iteratori utilizzati per attraversare il contenitore.

Il for anello gamma-based è espansa dal compilatore per:

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

La chiamata a begin->update() aggiunge un nuovo elemento al vettore, che invalida tutte iteratori nel contenitore, in modo che il ++begin è comportamento indefinito . In pratica, begin non fa più punti nel vettore (perché ha riallocato e liberato la vecchia memoria a cui puntava begin) quindi il successivo begin->update() chiama un riferimento a dereferenze un iteratore non valido, accedendo alla memoria liberata e seg-faulting.

Per farlo in modo sicuro, probabilmente si desidera utilizzare gli indici non iteratori:

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

Questa cattura le dimensioni all'inizio del ciclo e in modo che solo itera fino all'ultimo elemento che esiste all'avvio del ciclo, quindi i nuovi elementi aggiunti alla fine non verranno visitati.

Questo funziona ancora quando il vettore viene modificato perché non si memorizzano iteratori o puntatori agli elementi, ma solo un indice. Finché non rimuovi elementi dal vettore, l'indice è ancora valido anche dopo l'inserimento di nuovi elementi.

+0

Ho cambiato il mio ciclo in modo che fosse un ciclo fisso che accede tramite un iteratore per (indice automatico {0u}; indice BFritz

+3

No, non è sicuro. Non è un "ciclo forato" perché "vec.size()" cambia, e potrebbe andare in loop per sempre visitando le nuove entità man mano che vengono aggiunte. Capita di funzionare per il tuo esempio sopra perché 'Bullet' non modifica il vettore, ma visiti entrambe le entità durante il primo ciclo di aggiornamento. C'è una buona ragione per cui ho scritto il ciclo corretto come ho fatto io. Come ho detto: _ "Questa cattura la dimensione all'inizio del ciclo e quindi esegue solo l'iterazione fino all'ultimo elemento esistente all'avvio del loop, quindi i nuovi elementi aggiunti alla fine non verranno visitati." _ –

+0

Sei corretto , dopo aver eseguito alcuni test ho scoperto che non funziona come ho menzionato sopra. Userò le variabili pre-acquisite, come suggerito nella tua risposta. Sinceramente mi sento come se avessi dovuto sapere qual era il problema. Grazie per l'aiuto! – BFritz