1. 程式人生 > 其它 >C++裡使用vector中的iterator遍歷時需要注意的問題

C++裡使用vector中的iterator遍歷時需要注意的問題

------------恢復內容開始------------

遇到的實際問題:

最近程式設計遇到了一個非常有趣的問題,程式設計題目裡詢問在 Game::update() 里加入一個 entity 之後,這個 entity 是在加入的這一幀更新還是在下一幀(比如在這一幀裡玩家生成了一個子彈,這個子彈的位置是在這一幀更新,還是下一幀)?

於是我就寫了一個簡化的獨立測試,程式碼如下

(方便起見,用了全域性變數)

 1 std::vector<int> v;
 2 
 3 void addInteger(int i)
 4 {
 5     v.push_back(i);
 6 }
 7
8 int main() 9 { 10 addInteger(0); 11 addInteger(1); 12 addInteger(2); 13 auto it = v.begin(); 14 while (it != v.end()) 15 { 16 std::cout << (*it) << std::endl; 17 if ((*it) == 1) 18 addInteger(3); 19 it++; 20 } 21 return
0; 22 }
View Code

這裡面我想的是用 

1 std::cout << (*it) << std::endl;

代替呼叫iterator的部分程式碼,用 if 部分代替滿足某種條件後的加入entity操作

之後就有了一個神奇的報錯 “can't increment vector iterator past end”

但是我在主專案裡的程式碼並沒有這個報錯,我也很確認,的確是向裡面加入了一個新的 entity 的。

在詢問教授前,我對這個問題有一些想法,很可能是由於向vector里加入了新的元素,導致vector之前分配的空間不夠了,需要重新尋找並分配空間(這是vector比較低效的一個原因)所以iterator這個指標丟失了。

為了驗證這個猜想,我把程式碼寫成了這樣

 1 std::vector<int> v;
 2 
 3 void addInteger(int i)
 4 {
 5     v.push_back(i);
 6 }
 7 
 8 int main()
 9 {
10     v.reserve(4);
11     addInteger(0);
12     addInteger(1);
13     addInteger(2);
14     auto it = v.begin();
15     while (it != v.end())
16     {
17         std::cout << (*it) << std::endl;
18         if ((*it) == 1)
19             addInteger(3);
20         it++;
21     }
22     return 0;
23 }
View Code

加入了 v.reserve(4) 後果然不報錯了,此時初步確認這個問題就是因為 vector 的地址重新分配導致的。之後導師給我提供了更多的思路,需要區分 vector 的 size() 和 capacity() 這兩個概念,大小應該是當前vector所擁有的元素多少,而容量才是當前vector分配的記憶體空間。於是我做了簡單的測試,來測試 vector 會在哪幾個條件下改變capacity

 1 void charCapacityTester(int size=64)
 2 {
 3     std::vector<char> c;
 4 
 5     int preCap = c.capacity();
 6     for (int i = 0; i < size; ++i)
 7     {
 8         if (preCap != c.capacity())
 9         {
10             std::cout << "Size: " << c.size() << " Capacity: " << c.capacity() << std::endl;
11             preCap = c.capacity();
12         }
13         c.push_back('a');
14     }
15 }
View Code

輸出結果如圖

 

 所以當元素很少的時候,或者說vector增長很快的時候,vector 會頻繁重新分配,這就導致了效率低下和 iterator 不安全。解決辦法目前想到有幾種

1. 根據需要預分配

其實就是我在獨立測試裡用的reserve方法,比如根據這個變化趨勢和我遊戲裡entity的數量,可能預分配64的空間比較合適,因為64正好超過了63(不會因為加入一個新的entity就崩潰)而且和我實際的entity數量匹配。而且這個可以避免頻繁的重新分配vector空間。

2. 不用iterator

如果不想提前reserve的話也可以改用普通的for迴圈

1 for(int i = 0; i < v.size(); ++i)
2 {
3      if(v[i] == 1)
4     {
5         v.push_back(3);
6     }
7 }

這樣也能避免指標丟失。