C++ vector中的迭代器失效問題
vector中的迭代器失效問題
在使用vector的成員函式時,有兩個成員函式內部會出!](https://img-blog.csdnimg.cn/20181124093029161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01famlhbmppYW5qaWFv,size_16,color_FFFFFF,t_70)現迭代器失效的問題。分別是insert和erase而這兩個成員函式迭代器失效的原因並不相同。
insert
在我們使用insert的介面時,不會遇到迭代器的失效問題的影響,因為在insert中已經處理過l。而此處所說的就是在insert實現過程中出現的迭代器失效問題。
insert(iterator pos,value_type value);
vector類中的insert函式,在所傳迭代器位置的數前面插入一個數,返回值是所插入位置的迭代器。
現在我們對於該函式進行模擬實現,插入時有兩種情況
- 插入後長度不會超出當前容量
- 插入後超出當前容量
對於第一種,在容量足夠的情況下插入,進行正常的元素挪動和插入就行了。
對於第二種,當插入後的長度超出了容量的範圍。此時我們就需要先進行擴容。
如何擴容?
擴容時因為此時容量已不夠用,我們需要重新開闢一塊大於當前容量的空間。至於開闢多大的容量,這個我們可以自定義。只要能容納所插入的值。在vs下每次擴容的大小是之前容量的1.5倍,而在linux g++下每次開闢的容量是之前容量的2倍。開闢新空間後,將原空間的內容拷貝至新空間即可,再將物件中的指標指向新空間,再釋放原空間即可即可。此時應該注意,需要進行深拷貝,如果當vector中的元素為string vector時,如果進行是淺拷貝,在析構的時候會發生錯誤。
完成擴容後,此時卻出現了一個問題,我們所傳的迭代器位置是指向原空間的,而原空間的位置在我們進行擴容時已經釋放了,那意味著原來的迭代器位置已經是一個野指標,此時原先的迭代器位置已將失效。
如何解決?
我們只需在擴容時,將原迭代器位置的偏移量記錄下來,在新開闢的空間中找到原有的位置即可。
insert 的模擬實現
Iterator Insert(Iterator pos,T value) { assert(pos<=_finish); size_t offset=pos-_start;//增容更換空間後pos處的迭代器會失效,記錄其偏移量 if(_finish == _endofstorage) { size_t newCapacity = Capacity()==0?2:2*Capacity(); //防止第一次為空 Reserve(newCapacity); } pos = begin() + offset; Iterator end =this->end(); while(end != pos) { *end = *(end-1); end--; } *pos = value; _finish++; return pos; }
erase
在vector中erase 也會導致迭代器失效,不同於insert是在內部實現時會遇到的空間的更換造成迭代器失效,不會對於我們使用時造成影響,但erase會在使用時對我們造成一些影響。
iterator erase(iterator pos);
返回所刪除的數的下一個數的迭代器
int arr[] = { 1, 2, 3, 4, 5, 6 };
vector<int> nums(arr, arr + sizeof(arr) / sizeof(int));
vector<int>::iterator pos;
pos = nums.insert(nums.begin() + 1 , 9);
nums.erase(pos);
*pos = 10; //此處報錯
當我們刪除一個數後,再通過迭代器位置去訪問時,就會報錯
為甚麼會報錯
當我們刪除後將一個數刪除後者個位置的迭代器也就被刪除了,而我們如果再對刪除的迭代器位置進行修該訪問就會報錯
pos=nums.erase(pos);
//只要我們重新接收erase返回的迭代其位置,就可以了
*pos = 10;
//此時不會報錯
通過除錯,我們會發現,pos的傳入地址和傳出的地址完全一樣,並沒有發生變化
為甚麼迭代器會失效
通過vs底層實現的,我們可以看到他返回的迭代器不僅僅是傳入的位置,還進行了處理,可見vs在對迭代器進行了處理,以便於檢查,只要迭代器被刪除過後,就不能再使用,只能接受它返回的正確的迭代器。
從以下的底層程式碼可以看到vs對迭代器進行的檢查,在每次使用迭代器之前都對迭代器進行檢查。
將同樣的程式碼放在Linux下進行執行
int main()
7 {
8 int arr[] = { 1, 2, 3, 4, 5, 6 };
9 vector<int> nums(arr, arr + size
of(arr) / sizeof(int));
10
11
12 vector<int>::iterator pos;
13
14 pos = nums.insert(nums.begi
n() + 1 , 9);
15
16 nums.erase(pos);
17
18 *pos = 10;
19
20 for(auto& e: nums)
21 cout<<e<<" ";
22 cout<<endl;
23
24
25 return 0;
[[email protected] test2]$ ./test
1 10 3 4 5 6
在linux 下,程式碼正常執行並不會報錯,結果正確。這是因為在linux下對於迭代器的檢查並不嚴格,所以只要不超過迭代器的訪問區間,Linux下一般不會報錯。但是有時候我們必須要注意所得結果的正確性。
linux下的erase 的底層原始碼
template<typename _Tp, typename _Alloc>
133 typename vector<_Tp, _Alloc>::iterator
134 vector<_Tp, _Alloc>::
135 erase(iterator __position)
136 {
137 if (__position + 1 != end())
138 _GLIBCXX_MOVE3(__position + 1, end(), __position);
139 --this->_M_impl._M_finish;
140 _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
141 return __position;
(gdb)
142 }
//我們可以看到傳入和傳出的是一個position,而且也沒有太多的檢查
而在Linux的底層中在返回時並沒有對迭代器進行處理,在進入時也並沒有對於原迭代器進行檢查,只是對於迭代器的範圍進行了檢查。所以在不同的平臺下,檢查的方式不一樣,嚴格程度不一樣所以可能得出的結果也不同。
我們再來看這樣一份程式碼
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6 };
vector<int> nums(arr, arr + sizeof(arr) / sizeof(int));
vector<int>::iterator it = nums.begin();
while (it != nums.end())
{
if (*it % 2 == 0)
nums.erase(it);
++it;
}
for (int i = 0; i<nums.size(); i++)
cout << nums[i] << " ";
return 0;
}
毫無疑問在vs地下會報錯,在刪完2後,對it進行++時就會報錯。
但在linux下則會出現多種情況:
- 當我們所給刪除序列為 1 2 3 4 5 6
程式會報出段錯誤
[[email protected] test_erase]$ ./test
Segmentation fault (core dumped)
通過gdb除錯我們可以看到
在我們刪除2,4,之後,在進行6的刪除的時候因為,超出了迭代器的訪問位置而導致的段錯誤。
- 當我們所給刪除序列為 1 2 3 4 5
[[email protected] test_erase]$ ./test
1 3 5 [[email protected] test_erase]$
程式執行正常,且結果正確。
- 當我們所給刪除序列為 1 2 8 4 6 5
[[email protected] test_erase]$ ./test
1 8 6 5 [[email protected] test_erase]$
可以看到,在刪除的時候,並沒有將偶數項全部刪除完,是是因為在刪除過程中,會將後面的向前覆蓋,而返會的位置是當前的位置,下一次++時會將該位置跳過去,所以會出現這種錯誤。
在給出的三種錯誤種,我們可以看到有時侯結果執行正確,有時候錯誤,有時候程式掛掉。所以我們不能在有些時候因為結構正確就認為程式也是正確的。
所以erase 在配合迴圈使用時要注意
下面為正確的些法
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6 };
vector<int> nums(arr, arr + sizeof(arr) / sizeof(int));
vector<int>::iterator it = nums.begin();
while (it != nums.end())
{
if (*it % 2 == 0)
it=nums.erase(it); //使用一個迭代器來對返回的迭代器進行接受
else
++it; //因為返回的還是原位置,所以進行判斷的時候防止跳過
}
for (int i = 0; i<nums.size(); i++)
cout << nums[i] << " ";
return 0;
}