簡體   English   中英

在std :: vector :: begin()之前減少std :: vector :: iterator

[英]Decrementing std::vector::iterator before std::vector::begin()

在向量之一的“迭代時刪除”模式之后,我不明白為什么這個代碼有效,或者它是否正在使用未定義的行為:

守則

#include <vector>
#include <iostream>

int main(int argc, char* argv[], char* envz[])
{
  std::vector<std::string> myVec;
  myVec.push_back("1");
  myVec.push_back("2");
  myVec.push_back("3");

  for (std::vector<std::string>::iterator i = myVec.begin();
       i != myVec.end();
       ++i)
  {
    if ("1" == *i)
    {
      std::cout << "Erasing " << *i << std::endl;
      i = myVec.erase(i);
      --i;
      continue;
    }
    std::cout << *i << std::endl;
  }

  return 0;
}

輸出

>g++ -g main.cpp
>./a.out 
Erasing 1
2
3

問題

考慮for循環的第一次迭代:

  • i myVec.begin(),“指向” 1
  • 我們輸入條件塊。
  • 刪除1並將i設置為擦除元素之后的一個,即2 ,myVec.begin()現在也指向該元素
  • 我減少了i ,所以現在它指向... myVec.begin()之前的一個???

我很困惑為什么這似乎有效,正如輸出所證明的那樣,但是在減少迭代器方面感覺很可疑。 如果條件為if ("2" == *i) ,則此代碼很容易合理化,因為迭代器減量仍將其置於向量中的有效條目。 即如果我們有條件地擦除2i將被設置為指向3 ,但隨后手動遞減並因此指向1 ,然后是for循環增量,將其設置為再次指向3 有條不紊地擦除最后一個元素同樣容易遵循。

我還在嘗試什么

這個觀察使我假設在vector :: begin()之前遞減是冪等的,所以我嘗試了額外的減量,如下:

#include <vector>
#include <iostream>

int main(int argc, char* argv[], char* envz[])
{
  std::vector<std::string> myVec;
  myVec.push_back("1");
  myVec.push_back("2");
  myVec.push_back("3");

  for (std::vector<std::string>::iterator i = myVec.begin();
       i != myVec.end();
       ++i)
  {
    if ("1" == *i)
    {
      std::cout << "Erasing " << *i << std::endl;
      i = myVec.erase(i);
      --i;
      --i;      /*** I thought this would be idempotent ***/
      continue;
    }
    std::cout << *i << std::endl;
  }

  return 0;
}

但這導致了一個段錯誤:

Erasing 1
Segmentation fault (core dumped)

有人可以解釋為什么第一個代碼塊工作,特別是為什么擦除第一個元素后的單個減量有效?

不,您的代碼具有未定義的行為:如果i == myVec.begin() ,則i = myVec.erase(i); 導致i再次成為( myVec.begin()的新值myVec.begin() ,並且--i具有未定義的行為,因為它超出了迭代器的有效范圍。

如果你不想使用erase-remove習慣用法(即myVec.erase(std::remove(myVec.begin(), myVec.end(), "1"), myVec.end()) ),那么手動循環while-mutating看起來像這樣:

for (auto it = myVec.begin(); it != myVec.end(); /* no increment! */) {
  if (*it == "1") {
    it = myVec.erase(it);
  } else {
    ++it;
  }
}

無論如何,這里和原始代碼中的關鍵點是erase 使迭代器無效 ,因此在擦除之后必須使用有效值重新賦值迭代器。 我們實現這一點歸功於erase的返回值,這正是我們需要的新的有效迭代器。

這可能在某些編譯器中起作用,但在其他編譯器中可能會失敗(例如,編譯器可能實際上在運行時檢查您在begin()下沒有遞減並在這種情況下拋出異常 - 我相信至少有一個編譯器會這樣做但不會記住哪一個)。

在這種情況下,一般模式是不在for但在循環內增加:

  for (std::vector<std::string>::iterator i = myVec.begin();
       i != myVec.end();
       /* no increment here */)
  {
    if ("1" == *i)
    {
      std::cout << "Erasing " << *i << std::endl;
      i = myVec.erase(i);
      continue;
    }
    std::cout << *i << std::endl;
    ++i;
  }

使用向量時,錯誤的迭代實際上可能在更多情況下起作用,但如果您嘗試使用std::mapstd::set ,則會遇到非常糟糕的時間。

這里的關鍵是在遞減之后continue 通過調用它, ++i將在解除引用i之前由循環迭代觸發。

暫無
暫無

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

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