簡體   English   中英

std :: deque的錯誤?

[英]Bug with std::deque?

我試圖使用循環和迭代器從雙端隊列中刪除一個元素。 我正在關注在線示例,但看到了一個錯誤。

我正在使用g ++(GCC)4.8.3 20140911(Red Hat 4.8.3-9)。

這是代碼:

#include <iostream>
#include <deque>

using namespace std;

// Display the contents of a queue
void disp_deque(deque<int>& deque) {
  cout << "deque contains: ";
  for (auto itr = deque.begin(); itr!=deque.end(); ++itr)
    cout << *itr << ' ';
  cout << '\n';
}

int main(int argc, char** argv) {
  deque<int> mydeque;

  // Put 10 integers in the deque.
  for (int i=1; i<=10; i++) mydeque.push_back(i);
  disp_deque(mydeque);

  auto it = mydeque.begin(); 
  while (it!=mydeque.end()) {
    cout << "Checking " << *it << ',';
    // Delete even numbered values.
    if ((*it % 2) == 0) {
      cout << "Deleting " << *it << '\n';
      mydeque.erase(it++);
      disp_deque(mydeque);
    } else ++it;
  }
}

這很簡單 - 創建一個包含10個元素的列表並刪除偶數元素。

請注意以下內容(不包括絨毛):

if ((*it % 2) == 0) {
  mydeque.erase(it++);
} else it++;

這是使用迭代器進行刪除的推薦方法,這樣您的迭代器就不會像上面的鏈接中提到的那樣失效。

但是,當我運行它時,我得到以下內容:

$ ./test
deque contains: 1 2 3 4 5 6 7 8 9 10 
Checking 1,Checking 2,Deleting 2
deque contains: 1 3 4 5 6 7 8 9 10 
Checking 3,Checking 4,Deleting 4
deque contains: 1 3 5 6 7 8 9 10 
Checking 5,Checking 6,Deleting 6
deque contains: 1 3 5 7 8 9 10 
Checking 7,Checking 8,Deleting 8
deque contains: 1 3 5 7 9 10 
Checking 10,Deleting 10
deque contains: 1 3 5 7 9 
Checking 10,Deleting 10
deque contains: 1 3 5 7 
Checking 0,Deleting 0
deque contains: 1 3 5 
Checking 0,Deleting 0
deque contains: 1 3 
Checking 0,Deleting 0
deque contains: 1 
Checking 0,Deleting 0
deque contains: 
Checking 0,Deleting 0
Segmentation fault (core dumped)

看一看,它看起來很不錯,直到刪除8.實際上,數字9被完全跳過並且從未檢查過! 我期望應該發生的是:

$ ./test
deque contains: 1 2 3 4 5 6 7 8 9 10 
Checking 1,Checking 2,Deleting 2
deque contains: 1 3 4 5 6 7 8 9 10 
Checking 3,Checking 4,Deleting 4
deque contains: 1 3 5 6 7 8 9 10 
Checking 5,Checking 6,Deleting 6
deque contains: 1 3 5 7 8 9 10 
Checking 7,Checking 8,Deleting 8
deque contains: 1 3 5 7 9 10 
Checking 9,Checking 10,Deleting 10
deque contains: 1 3 5 7 9 

事實上,這正是我將代碼更改為此時所獲得的:

if ((*it % 2) == 0) {
  it=mydeque.erase(it);
} else it++;

那么,為什么一種方法有效,而另一種方法無效? 有人能解釋一下嗎?

即使我創建一個要刪除的臨時迭代器,我也會看到完全相同的問題輸出:

  while (it!=mydeque.end()) {
    cout << "Checking " << *it << ',';
    auto tmp_it = it++;
    // Delete even numbered values.
    if ((*tmp_it % 2) == 0) {
      cout << "Deleting " << *tmp_it << '\n';
      cout << "IT before delete: " << *it << '\n';
      mydeque.erase(tmp_it);
      cout << "IT after delete: " << *it << '\n';
      disp_deque(mydeque);
    } 
  }

在這里,我將它的副本存儲在tmp_it中,然后遞增它。 我添加了一些調試語句,看到了一些非常奇怪的東西:

...
deque contains: 1 3 5 6 7 8 9 10 
Checking 5,Checking 6,Deleting 6
IT before delete: 7
IT after delete: 7
deque contains: 1 3 5 7 8 9 10 
Checking 7,Checking 8,Deleting 8
IT before delete: 9
IT after delete: 10
deque contains: 1 3 5 7 9 10 
Checking 10,Deleting 10
IT before delete: 10
IT after delete: 10
...

但是,刪除元素8使其指向元素10,跳過9! 在先前的刪除中,它指向前一個元素(例如,當刪除6時,它指向刪除之前和之后的7)。

我查看了deque的實現,並在“Iterator Validity”下面看到了(強調我的):

迭代器有效性如果擦除操作包括序列中的最后一個元素,則結束迭代器以及引用已擦除元素的迭代器,指針和引用無效。 如果擦除包括第一個元素但不包括最后一個元素,則只有那些涉及擦除元素的元素無效。 如果它發生在deque中的任何其他位置,則與容器相關的所有迭代器,指針和引用都將失效。

那么這是否意味着在我的代碼中,即使我在刪除之前對其進行了后期增量,我的迭代器也會失效? 即除了我刪除的迭代器之外的迭代器是無效的?

如果是這樣,那么它很好,但它似乎是一個鮮為人知的bug。 這意味着在使用deque時,循環中迭代器刪除的常見實現無效。

來自deque::erase() cppreference:

除非擦除的元素位於容器的末尾或開頭,否則所有迭代器和引用都將失效 ,在這種情況下,只有迭代器和對擦除元素的引用才會失效。

所有迭代器。 他們全部。 當你這樣做:

mydeque.erase(it++);

你后遞增it並不重要,新的迭代器也是無效的。 這正是erase() 返回的原因

Iterator跟隨最后刪除的元素。 如果迭代器pos引用最后一個元素,則返回end()迭代器。

這樣你就可以做到:

it = mydeque.erase(it); // erase old it, new it is valid

雖然更好的方法是通過使用擦除刪除習慣來完全避免這種錯誤來源:

mydeque.erase(
   std::remove_if(mydeque.begin(), mydeque.end(), [](int i){return i%2 == 0; }),
   mydeque.end()
);

有關迭代器失效的更多信息,另請參閱此問題

您引用的代碼僅用於關聯容器setmap等)。

Scott Meyers的有效STL第9項(恰當地命名為“在擦除選項中仔細選擇”)顯示了它是如何為序列容器 (vector,deque,string)完成的

for (SeqContainer<int>::iterator it = c.beqin(); it != c.end();) {
    if (predicate(*it)){
        it = c.erase(it); // keep it valid by assigning
    }                     // erase's return value to it
    else ++it;
}

這里, erase()返回值正是我們所需要的:它是一個有效的迭代器,指向擦除完成后擦除元素后面的元素。

那么這是否意味着在我的代碼中,即使我在刪除之前對其進行了后期增量,我的迭代器也會失效?

這正是它的意思。 該失效是否會對結果產生任何影響,這取決於運行時庫中dequeue的實現。 在許多情況下它也可能會很好,然后突然失敗,就像你的8。

post增量技巧僅適用於容器,其中erase僅使擦除元素的迭代器無效(如list s和set s)。 在這些情況下,post增量會為下一個元素創建一個迭代器,並刪除元素之前執行此操作。 因此,對下一個元素的迭代器不受與擦除相關的無效的影響。 但是,在dequeue的情況下,規范說所有迭代器都是無效的。

C ++ 14引入了通用的erase_if算法,該算法可以與所有類型的標准容器一起正常工作。

http://en.cppreference.com/w/cpp/experimental/deque/erase_if

這相當於@Barry提供的最后一個代碼塊:

#include <experimental/deque>
std::experimental::erase_if(mydeque, [](int i){return i%2 == 0; });

與直接擦除/刪除 - 如果模式相比,使用此通用算法也更好,因為如果您決定將容器替換為std::set ,則不需要更新處理刪除的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM