2011-04-15 90 views
12

我找不到如何做到這一點的實例,所以我希望有人能幫助我。我有一個在類中定義的地圖如下:帶刪除的C++映射迭代

std::map<std::string, TranslationFinished> translationEvents; 

TranslationFinished是boost :: function。我有一個方法,我的課的一部分,通過這個地圖迭代,調用每個功能,如下所示:

void BaseSprite::DispatchTranslationEvents() 
{ 
    for(auto it = translationEvents.begin(); it != translationEvents.end(); ++it) 
    { 
     it->second(this); 
    } 
} 

但是有可能爲一個由it->second(this);調用的函數從translationEvents地圖上刪除元素(通常本身使用下面的函數):

bool BaseSprite::RemoveTranslationEvent(const std::string &index) 
{ 
    bool removed = false; 
    auto it = translationEvents.find(index); 
    if (it != translationEvents.end()) 
    { 
     translationEvents.erase(it); 
     removed = true; 
    } 
    return removed; 
} 

這樣做會導致調試斷言失敗時DispatchTranslationEvents()試圖遞增迭代器。有沒有一種方法可以在迭代過程中調用函數調用可能從地圖中移除元素的可能性,從而安全地遍歷地圖?

在此先感謝

編輯:意外C /鈀錯誤的移除事件代碼。現在修復。

回答

4

閱讀所有其他答案後,我在這裏有一個優勢...但它在這裏。

但是它可能會調用一個函數 - > second(this);從translationEvents地圖(通常本身)

如果這是真的,那就是刪除元素的回調可以從容器中取出任何元素,你不可能解決從循環本身這個問題。

刪除當前回調

在簡單情況下的回調只能清除本身,你可以使用不同的方法:

// [1] Let the callback actually remove itself 
for (iterator it = next = m.begin(); it != m.end(); it = next) { 
    ++next; 
    it->second(this); 
} 
// [2] Have the callback tell us whether we should remove it 
for (iterator it = m.begin(); it != m.end();) { 
    if (!it->second(this)) {     // false means "remove me" 
     m.erase(it++); 
    } else { 
     ++it; 
    } 
} 

在這兩個選項,我顯然會更喜歡[2 ],因爲您正在將處理程序的實現與回調分離。也就是說,[2]中的回調對於它所在的容器一無所知。 [1]具有更高的耦合度(回調知道容器),並且由於容器在代碼中的多個位置發生更改而導致難以推理。一段時間以後,你甚至可以回頭看看代碼,認爲這是一個奇怪的循環(不記回調刪除自身),並將其重構爲更多的東西明智作爲for (auto it = m.begin(), end = m.end(); it != end; ++it) it->second(this);

刪除等回調

對於更復雜的問題可以刪除任何其他回調,這一切都取決於你可以做出的妥協。在簡單情況下,它僅完整迭代後刪除其它回調,你可以提供一個單獨的成員函數,將讓元素去掉,然後循環完成後立即將它們全部:

void removeElement(std::string const & name) { 
    to_remove.push_back(name); 
} 
... 
for (iterator it = m.begin(); it != m.end(); ++it) { 
    it->second(this);  // callback will possibly add the element to remove 
} 
// actually remove 
for (auto it = to_remove.begin(); it != to_begin.end(); ++it) { 
    m.erase(*it); 
} 

如果需要立即刪除元素(即,如果尚未調用它們,則即使在此迭代中它們也不應被調用),那麼可以通過在執行調用之前檢查它是否被標記爲刪除來修改該方法。標記可以通過兩種方式完成,其中的通用方法是將容器中的值類型更改爲pair<bool,T>,其中bool表示它是否存在。如果在這種情況下,所包含的對象是可以改變的,你可能只是這樣做:

void removeElement(std::string const & name) { 
    auto it = m.find(name);   // add error checking... 
    it->second = TranslationFinished(); // empty functor 
} 
... 
for (auto it = m.begin(); it != m.end(); ++it) { 
    if (!it->second.empty()) 
     it->second(this); 
} 
for (auto it = m.begin(); it != m.end();) { // [3] 
    if (it->second.empty()) 
     m.erase(it++); 
    else 
     ++it; 
} 

注意,因爲回調可去除在容器中的任何元素,你無法抹去,當您去,爲當前回調可能會刪除已訪問的迭代器。然後再一次,你可能不會在乎留下空的函子一段時間,所以它可能沒關係只是忽略它,並隨時執行erase。已經訪問過的標有要刪除的元素將在下一個步驟中清除。

6

一般來說,在迭代過程中修改集合是不被贊同的。當集合被修改時,許多集合都會使迭代器失效,其中包括C#中的許多容器(我知道你在使用C++)。您可以創建一個在迭代過程中想要移除的事件向量,然後將其移除。

+0

C#的集合類在這方面很糟糕。如果他們被修改,他們將使所有*失效。 C++容器在這方面的表現通常要好得多,並且通常只會使特定的迭代器無效,因此您可以在迭代時刪除 – jalf 2011-04-15 06:42:05

2

在迭代過程中應該有一種方法可以擦除元素,也許有點棘手。

for(auto it = translationEvents.begin(); it != translationEvents.end();) 
{ 
    //remove the "erase" logic from second call 
    it->second(this); 
    //do erase and increase the iterator here, NOTE: ++ action is very important 
    translationEvents.erase(it++);   
} 

一旦元素被刪除,迭代器將無效,因此在刪除元素後您不能再使用該迭代器執行增加操作。但是,移除一個元素不會影響地圖實現IIRC中的其他元素。所以後綴++會首先複製iter並在此之後立即增加迭代器,然後返回拷貝值,這意味着迭代器在刪除操作之前增加,這對您的需求應該是安全的。

+0

在迭代過程中擦除是一種常見模式,回調通常使用返回值來實現,該返回值確定需要移除:'if(it-> second(this)){translationEvents.erase(it ++); } else ++ it',其中每個回調將在這個執行後返回'true'(如果它有效)。 – 2011-04-15 07:55:59

1

問題是++it遵循可能的刪除。這對你有用嗎?

void BaseSprite::DispatchTranslationEvents() 
{ 
    for(auto it = translationEvents.begin(), next = it; 
     it != translationEvents.end(); it = next) 
    { 
     next=it; 
     ++next; 
     it->second(this); 
    } 
} 
+1

這個問題表明,可以刪除除當前元素之外的其他元素。如果下一個元素被刪除,這不會真的起作用。 – Anton 2011-04-15 03:13:30

3

我的解決方案是首先創建一個臨時容器,並與原來的容器交換。然後,您可以通過臨時容器進行迭代,然後將想要保留的容器插入原始容器。如果不想被依然古色古香,並返回false即可去除

void BaseSprite::DispatchTranslationEvents() 
{ 
    typedef std::map<std::string, TranslationFinished> container_t; 

    container_t tempEvents; 
    tempEvents.swap(translationEvents); 

    for(auto it = tempEvents.begin(); it != tempEvents.end(); ++it) 
    { 
     if (true == it->second(this)) 
      translationEvents.insert(it); 
    } 
} 

而且TranslationFinished函數應返回true。

bool BaseSprite::RemoveTranslationEvent(const std::string &index) 
{ 
    bool keep = false; 
    return keep; 
} 
2

你可以推遲去除,直到調度循環:

typedef boost::function< some stuff > TranslationFunc; 

bool BaseSprite::RemoveTranslationEvent(const std::string &index) 
{ 
    bool removed = false; 
    auto it = translationEvents.find(index); 
    if (it != translationEvents.end()) 
    { 
     it->second = TranslationFunc(); // a null function indicates invalid event for later 
     removed = true; 
    } 
    return removed; 
} 

防止在循環本身調用一個無效的事件,並清理任何「刪除」事件:

void BaseSprite::DispatchTranslationEvents() 
{ 
    for(auto it = translationEvents.begin(); it != translationEvents.end();) 
    { 
     // here we invoke the event if it exists 
     if(!it->second.empty()) 
     { 
      it->second(this); 
     } 

     // if the event reset itself in the map, then we can cleanup 
     if(it->second.empty()) 
     { 
      translationEvents.erase(it++); // post increment saves hassles 
     } 
     else 
     { 
      ++it; 
     } 
    } 
} 

一個明顯的警告是,如果一個事件被迭代,然後被刪除,它將不會有機會在當前調度循環期間被再次迭代以被刪除。

這意味着該事件的實際刪除將被推遲到下次調度循環運行時。

+0

+1,這個想法可以通過使用第二個容器(通過引用傳遞給回調函數)進行擴展,如果需要刪除它們,他們會在其中插入一個迭代器給自己(或其他迭代器)。執行循環完成後,您可以迭代候選列表並將它們逐出。回調的實際接口可以保持不變,方法是在保存容器的對象上添加一個'markForDeletion'方法。 – 2011-04-15 07:59:59

7

map::erase使被刪除的迭代器(顯然)無效,但不會映射其他地圖。 這意味着:

  • ,如果你刪除的任何元素其他比當前,你是安全的,並
  • 如果刪除當前元素,你必須首先獲取下一個迭代器,所以你可以繼續迭代(這就是爲什麼大多數容器的erase函數返回下一個迭代器)。 std::map的沒有,所以你必須手動執行此操作)

假設你永遠只刪除當前元素,那麼你可以簡單地改寫這樣的循環:

for(auto it = translationEvents.begin(); it != translationEvents.end();) 
{ 
    auto next = it; 
    ++next; // get the next element 
    it->second(this); // process (and maybe delete) the current element 
    it = next; // skip to the next element 
} 

否則(如果該功能可能刪除任何元素),它可能會更復雜一點。