2012-05-24 33 views
2

我想對矢量做一些操作。只在某些情況下調用向量擦除。當我使用vector :: erase時,有什麼原因會導致崩潰?

這裏是我的代碼

while(myQueue.size() != 1) 
{ 
    vector<pair<int,int>>::iterator itr = myQueue.begin(); 
    while(itr != myQueue.end()) 
    { 
     if(itr->first%2 != 0) 
      myQueue.erase(itr); 
     else 
     { 
      itr->second = itr->second/2; 
      itr++; 
     } 
    } 
} 

我越來越崩潰第二iteration.And我得到這個崩潰與消息向量迭代器不兼容。

什麼可能是崩潰的原因?

+2

而問題是......? – ScarletAmaranth

+0

這個問題很難理解。你可以重新說明嗎? – juanchopanza

+0

問題只是缺少問號。最後一行。 – Nick

回答

8

如果erase()被調用,則迭代器失效,並且該迭代器在循環的下一次迭代中被訪問。 std::vector::erase()返回擦除迭代之後的下一個迭代:

itr = myQueue.erase(itr); 
2

鑑於一個迭代範圍[b, e)其中b是一個迭代i開始和e一個過去的範圍的端部爲一個矢量的擦除操作中的某處範圍將使從ie的所有迭代器失效。這就是爲什麼當您致電erase時需要非常小心。該erase成員不會返回一個新的迭代器你可以在你的後續操作,你應該使用這些功能:

itr = myQueue.erase(itr); 

另一種方式是交換i元素和最後一個元素,然後刪除最後。這是更有效的,因爲更少的元素移動數量超過i是必要的。

myQueue.swap(i, myQueue.back()); 
myQueue.pop_back(); 

此外,從它的外觀,你爲什麼使用vector?如果您需要queue,則不妨使用std::queue

+1

交換版本改變了元素的順序(不知道是否重要),當名稱是myQueue時,它所使用的操作不被真正的FIFO隊列('std :: queue')支持 –

+0

你在說什麼操作?另外,'vector'不是*排序的容器。我沒有看到你的反對意見。我的最後一段有相同的問題btw - 爲什麼OP使用'vector'如果(s)他需要一個隊列。 – dirkgently

+0

這兩點是:矢量有一個順序,元素的插入順序。如果這是所需語義的一部分,那麼交換將改變該順序。第二點是,問題中的操作不能應用於'std :: queue',所以你使用隊列的建議是沒有意義的。 –

2

這是未定義的行爲。特別是,一旦你刪除了一個迭代器,它就變成無效的,你不能再使用它了。展開循環的慣用方法是這樣的:

for (auto it = v.begin(); it != v.end();) { 
    if (it->first % 2 != 0) 
     it = v.erase(it); 
    else { 
     it->second /= 2; 
     ++it; 
    } 
} 

但話又說回來,這將是更有效和更地道不推出自己的循環,而使用的算法:

v.erase(std::remove_if(v.begin(), 
         v.end(), 
         [](std::pair<int,int> const & p) { 
          return p.first % 2 != 0; 
         }), 
     v.end()); 
std::transform(v.begin(), v.end(), v.begin(), 
       [](std::pair<int,int> const & p) { 
        return std::make_pair(p.first, p.second/2); 
       }); 

的這種方法的優點在於擦除時元素的副本數量較少(每個在該範圍內剩餘的有效元素將被複制不超過一次),並且很難使其錯誤(即濫用無效的迭代器...)缺點是沒有remove_if_and_transform,所以這是一個雙通道算法,如果存在大量元素。

+0

我使用Boost.Range添加了單向傳球時尚到我的答案。它可能更高效,但看起來並不比OP的原始代碼好。 –

2

在修改循環時迭代通常很棘手。

因此,有一個特定的C++習慣用法可用於非關聯序列:擦除刪除習慣用法。

它結合了erase方法的範圍過載使用remove_if算法:

myQueue.erase(
    std::remove_if(myQueue.begin(), myQueue.end(), /* predicate */), 
    myQueue.end()); 

其中謂詞表達爲一個典型的函子對象或使用新的C++ 11 lambda語法。

// Functor 
struct OddKey { 
    bool operator()(std::pair<int, int> const& p) const { 
     return p.first % 2 != 0; 
    } 
}; 

/* predicate */ = OddKey() 

// Lambda 
/* predicate */ = [](std::pair<int, int> const& p) { return p.first % 2 != 0; } 

lambda表單更簡潔,但可能更少自我記錄(無名稱),並且僅在C++ 11中可用。根據你的口味和限制,挑選最適合你的人。


有可能提升你的寫作方式:使用Boost.Range

typedef std::vector< std::pair<int, int> > PairVector; 

void pass(PairVector& pv) { 
    auto const filter = [](std::pair<int, int> const& p) { 
     return p.first % 2 != 0; 
    }; 
    auto const transformer = [](std::pair<int, int> const& p) { 
     return std::make_pair(p.first, p.second/2); 
    }; 

    pv.erase(
     boost::transform(pv | boost::adaptors::filtered(filter), 
         std::back_inserter(pv), 
         transformer), 
     pv.end() 
    ); 
} 

你可以找到transform和文檔中的filtered adaptor,其他許多人。

相關問題