[英]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)
,則此代碼很容易合理化,因為迭代器減量仍將其置於向量中的有效條目。 即如果我們有條件地擦除2
, i
將被設置為指向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::map
或std::set
,則會遇到非常糟糕的時間。
這里的關鍵是在遞減之后continue
。 通過調用它, ++i
將在解除引用i
之前由循環迭代觸發。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.