2013-06-30 69 views
0

我不太確定我需要一個對象池,但它似乎是最可行的解決方案,但有一些不想要的缺點與它相關聯。我正在製作一個遊戲,實體存儲在對象池中。這些實體不是直接分配給新的,而是std::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隊列,這將讓這個標識將被重新-used,最後它的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實體消滅他們。如果我能夠從池中獲取實體,或者存儲指向實際實體的指針副本,該怎麼辦?如何在銷燬時將所有其他重複實體置空?

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 
+0

所以你的問題是'object1'和'object2'指向相同的實際實體?但是,這不就是擁有一個游泳池嗎?如果你想避免使用'object1',那麼你必須'NULL'它。 – Manuzor

+0

@Manuzor我感覺有點遲鈍,但是無論如何無視指針對我來說似乎有點黑客。 –

+0

順便說一下,你可能想看看[placement new](http://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new),它允許你調用構造函數和'Entity'類的析構函數,而不必使用'new'或在堆棧上分配對象。這樣'Entity'類不需要特別處理創建和清理之後。 – Manuzor

回答

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的副本。

另一種方法是使用auto_ptr<>(在C++'98,​​的C++ - 11中),它保證了它們的內部指針在釋放時將被設置爲NULL。如果將它與在實體類中重載運算符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)。另一個嘗試是放棄在這些空閒插槽中浪費的內存,並分配另一個塊(DEFAULT_SIZE)。

同樣,這一切都取決於您所遇到的實際用途。唯一的方法就是試驗和測量。

最後,請記住,您可以修改類實體以透明地支持對象池。這樣做的一個好處是你可以試驗它是否是一個更好的方法。

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

    void operator delete(void * entity) 
    { 
    } 

private: 
    Pool pool; 
}; 

希望這會有所幫助。

+0

你可以在帖子中看到我的編輯,它是銷燬它們時的具體NULL實體 –

+0

除了使用auto_ptr/unique_ptr(C++ 11)之外,您的問題沒有最終的解決方案。 – Baltasarq

+0

我已將auto_ptr/unique_ptr添加到我的答案中。 – Baltasarq

1

如果你想要一個Entity實例只能通過create到達,你將不得不隱藏get函數(它在你的原始代碼中不存在:))。

我想加入這種安全性,以你的遊戲是相當多的矯枉過正,但如果你真的需要一個機制來控制訪問內存中的某些部分,我會考慮像handleweak pointer而不是返回東西一個原始指針。此weak pointer將包含向量/映射上的索引(您存儲在除weak pointer之外的其他任何地方都無法訪問的位置),而該映射又包含實際的Entity指針以及指示弱指針是否仍然有效的小散列值。

下面是一些代碼,所以你明白我的意思:

struct WeakEntityPtr; // Forward declaration. 
struct WeakRefIndex { unsigned int m_index; unsigned int m_hash; }; // Small helper struct. 
class Entity { 
    friend struct WeakEntityPtr; 
private: 
    static std::vector< Entity* > s_weakTable(100); 
    static std::vector<char> s_hashTable(100); 
    static WeakRefIndex findFreeWeakRefIndex(); // find next free index and change the hash value in the hashTable at that index 
struct WeakEntityPtr { 
private: 
    WeakRefIndex m_refIndex; 
public: 
    inline Entity* get() { 
     Entity* result = nullptr; 

     // Check if the weak pointer is still valid by comparing the hash values. 
     if (m_refIndex.m_hash == Entity::s_hashTable[ m_refIndex.m_index ]) 
     { 
      result = WeakReferenced<T>::s_weakTable[ m_refIndex.m_index ]; 
     } 

     return result; 
    } 
} 

這不,雖然一個完整的例子(你將不得不採取適當的(複印件)構造的照顧,賦值操作等等等等。 ),但它應該能讓你明白我在說什麼。

但是,我想強調的是,我仍然認爲一個簡單的池就足以滿足您在這種情況下要做的事情。你必須讓代碼的其餘部分與實體良好地配合,這樣他們纔不會重用那些他們不應該重複使用的對象,但我認爲這樣做比較容易,並且可以比整個對象更清晰地維護它們handle /上面的故事weak pointer

+0

std :: shared_ptr呢? [像這樣。](http://ideone.com/mffxDr)不是手動銷燬實體,而是爲您完成。如果需要手動刪除,可以使用重置,前提是沒有其他實體引用。 –

+0

當然,但我理解這種方式,你想控制誰可以實際使用實體實例,誰不能。當不確定誰是原始指針的所有權時,通常使用'shared_ptr',即不清楚誰將銷燬實例。你的例子表明任何人都可以堅持到實際的實體實例,只要他們想要,即使它已經被銷燬。一旦創建了新的共享指針,以前的共享指針就可以再次使用。事實上,它幾乎和一開始一樣,你不必再打電話銷燬了。通過上面描述的'弱指針'... – Manuzor

+0

...,你可以修補你發出的弱指針實例變爲無效(並且只返回'nullptr')。這樣,即使你調用'create',然後'destroy'和'create',第一個'弱指針'也是無效的(因爲散列值已經改變),但是新的'弱指針'是有效的。 – Manuzor