0

オブジェクト プールが必要かどうかはよくわかりませんが、これが最も実行可能なソリューションのように思えますが、これには望ましくない短所がいくつかあります。エンティティがオブジェクト プール内に格納されるゲームを作成しています。これらのエンティティは new で直接割り当てられず、代わりに astd::dequeがそれらのメモリを処理します。

これは私のオブジェクトプールが多かれ少なかれどのように見えるかです:

struct Pool
{  
    Pool()
        : _pool(DEFAULT_SIZE)
    {}

    Entity* create()
    {
        if(!_destroyedEntitiesIndicies.empty())
        {
            _nextIndex = _destroyedEntitiesIndicies.front();
            _destroyedEntitiesIndicies.pop();
        }

        Entity* entity = &_pool[_nextIndex];
        entity->id = _nextIndex;
        return entity;
    }

    void destroy(Entity* x)
    {
        _destroyedEntitiesIndicies.emplace(x->id);
        x->id = 0;
    }

private:

    std::deque<Entity> _pool;
    std::queue<int> _destroyedEntitiesIndicies;
    int _nextIndex = 0;
};

エンティティを破棄すると、ID がキューに追加され_destroyedEntitiesIndicies、ID が再利用され、最後に ID が 0 に設定されます。これに対する唯一の落とし穴は、破棄する場合です。エンティティを作成してすぐに新しいエンティティを作成すると、以前に破棄されたエンティティが更新され、作成されたばかりのエンティティと同じになります。

すなわち

Entity* object1 = pool.create(); // create an object
pool.destroy(object1); // destroy it

Entity* object2 = pool.create(); // create another object
// now object1 will be the same as object2

std::cout << (object1 == object2) << '\n'; // this will print out 1

これは私には正しくないようです。どうすればこれを回避できますか? 明らかに、上記はおそらく起こらないでしょう (次のフレームまでオブジェクトの破壊を遅らせるため)。しかし、これは、エンティティの状態をファイルに保存する際、またはそれらの行に沿って何らかの障害を引き起こす可能性があります。

編集:

それらを破壊するために NULL エンティティを実行したとしましょう。プールからエンティティを取得したり、実際のエンティティへのポインタのコピーを保存したりできたらどうしますか? 破棄されたときに他のすべての重複エンティティを NULL にするにはどうすればよいですか?

すなわち

Pool pool;
Entity* entity = pool.create();

Entity* theSameEntity = pool.get(entity->getId());

pool.destroy(entity);

// now entity == nullptr, but theSameEntity still points to the original entity
4

2 に答える 2

1

この質問にはさまざまな部分があるようです。どれどれ:

(...) エンティティを破棄してすぐに新しいエンティティを作成すると、以前に破棄されたエンティティは、作成されたばかりのエンティティと同じになるように更新されます。これは私には正しくないようです。どうすればこれを回避できますか?

このメソッドを次のように変更できます。

void destroy(Entity* x)
    {
        _destroyedEntitiesIndicies.emplace(x->id);
        x->id = 0;
    }

することが:

void destroy(Entity *&x)
    {
        _destroyedEntitiesIndicies.emplace(x->id);
        x->id = 0;
        x = NULL;
    }

これにより、発生している特定の問題を回避できます。ただし、問題全体が解決するわけではありません。NULL に更新されないコピーを常に持つことができます。

もう 1 つの方法はauto_ptr<>(C++'98 ではunique_ptr<>、C++-11 で) 使用することです。これにより、解放時に内部ポインターが NULL に設定されることが保証されます。これを Entity クラスの new および delete 演算子のオーバーロードと組み合わせると (以下を参照)、非常に強力なメカニズムを実現できます。など、標準の新しいバージョンである C++-11 にはいくつかのバリエーションがありshared_ptr<>、これも役立つ場合があります。あなたの具体例:

auto_ptr<Entity> object1( new Entity ); // calls pool.create()
object1.release();                      // calls pool.destroy, if needed

auto_ptr<Entity> object2( new Entity ); // create another object

// now object1 will NOT be the same as object2
std::cout << (object1.get() == object2.get()) << '\n'; // this will print out 0

cplusplus.comwikipedia、およびHerb Shutterの非常に興味深い記事など、さまざまな情報源が考えられます。

オブジェクト プールに代わるものはありますか?

オブジェクト プールは、オブジェクトの最大数がわかっている状況で、コストがかかる継続的なメモリ操作を回避するために作成されます。あなたの場合、私が考えることができるオブジェクトプールに代わるものはありません。正しい設計を試みていると思います。ただし、作成と破棄が多い場合は、オブジェクト プールを使用しないのが最善の方法かもしれません。実験と時間を測定せずに言うことは不可能です。

実装については、さまざまなオプションがあります。

そもそも、_destroyedEntitiesIndicies を使用しているため、メモリ割り当てを回避することでパフォーマンス上の利点が得られるかどうかは明確ではありません (とにかく、オブジェクトを破棄するたびにメモリを割り当てる可能性があります)。これにより、単純な割り当てとは対照的に十分なパフォーマンスが得られる場合は、コードを試してみる必要があります。_destroyedEntitiesIndicies を完全に削除して、それらが不足している場合にのみ空のスロットを見つけようとすることができます (_nextIndice >= DEFAULT_SIZE )。もう 1 つの試みは、空きスロットで無駄になっているメモリを破棄し、代わりに別のチャンク (DEFAULT_SIZE) を割り当てることです。

繰り返しますが、それはすべてあなたが経験している実際の使用に依存します. それを見つける唯一の方法は、実験と測定です。

最後に、オブジェクト プールを透過的にサポートするかどうかに関係なく、クラス Entity を変更できることを覚えておいてください。これの利点は、それが本当に優れたアプローチであるかどうかを実験できることです。

class Entity {
public:
    // more things...
    void * operator new(size_t size)
    {
        return pool.create();
    }

    void operator delete(void * entity)
    {
    }

private:
    Pool pool;
};

お役に立てれば。

于 2013-06-30T10:28:05.067 に答える