2015-09-25 9 views
6

私には分からないセグメンテーションの問題があります。それは私が取り組んでいる小さなゲームエンジンのためのEntityManagerからです。 Ship Entityを追加できますが、船は1 Bullet Entityを追加できますが、1を超えると追加しようとするとsegfaultsとなりますBullet。私は今、この1日のためにこれをfiggureしようとしています。以下は、実際のコードからの抜粋です。実際のコードで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; 
} 

、すべてが自分自身の.h/.cppのファイルを、代わりに構造体のクラスであるが、問題は同じです。 SIGSEGV(セグメンテーション障害)

は、セグメンテーション違反はentity->update();ライン上EntityManager::update()で起こる: 出力//完了ループ#1 //弾丸//ブレットCTOR //信号を加算弾丸//ブレットCTOR追加 `あります。

+1

エンティティの 'manager'ポインタを更新するために' EntityManager'はカスタム移動操作を必要とします。 – dyp

+1

エンティティを更新すると、そのループはイテレータを無効にするエンティティを追加します。ループ中にベクターに追加することはできません。 – Galik

+1

[エンジンではないゲームを書く](http://geometrian.com/programming/tutorials/write-games-not-engines/)は読むのが良いことです。つまり、即座にイベントを追加したり、イベントを即座に破棄したりするのではなく、更新ループ後、イベントなど、適切なイベントを介して遅延操作を行い、イテレータを無効にすることはできません。 – aslg

答えて

13

問題は、このループはベクトルを変更することです:

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

あなたは、コンテナを横断するために使用されているイテレータを無効に新しい要素を追加するベクトルを変更すると、それを反復処理忙しいです。

範囲ベースforループにコンパイラによって拡張される。

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

begin->update()への呼び出しを容器にすべてのイテレータを無効ベクターに新しい要素を追加し、そう++beginは未定義の動作であります。実際には、beginは、(beginが指していた古いメモリを再割り当てして解放したため)ベクトルを指していないので、次のbegin->update()は無効なイテレータを参照し、解放されたメモリとsegフォルトを呼び出します。

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

これは、ループの開始時にサイズとループの開始時に存在する最後の要素までこれだけ繰り返し処理を取り込み、:

あなたはおそらくインデックスではないイテレータを使用したい安全にそれを行うために、最後に追加された新しい要素は訪問されません。

これは、イテレータまたは要素へのポインタを格納していないため、インデックスのみを格納するため、ベクトルが変更されても機能します。ベクトルから要素を削除しない限り、新しい要素を挿入してもインデックスは有効です。

+0

私のループは、(自動インデックス{0u};インデックス BFritz

+3

いいえ、それは安全ではありません。 'vec.size()'が変更され、追加されたときに新しいエンティティを永遠に訪れることができるので、 "fixed for loop"ではありません。 'Bullet'はベクトルを変更しないので、最初の更新ループの間に両方のエンティティにアクセスするので、上の例ではうまく動作します。私がやったように訂正したループを書いたのは良い理由があります。私が言ったように_ _ "これはループの開始時にサイズを取得するので、ループの開始時に最後にある要素まで反復するので、最後に追加された新しい要素は訪れません。" _ –

+0

あなたは正しいいくつかのテストを実行した後、私は上記のように動作しないことがわかった。私はあなたの答えに示唆したように、事前にキャプチャされた変数を使用します。私は正直なところ、問題が何であるかを知っておくべきだったように感じます。ご協力いただきありがとうございます! – BFritz

関連する問題