------------恢復內容開始------------
遇到的實際問題:
最近編程遇到了一個非常有趣的問題,編程題目里詢問在 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 }
這里面我想的是用
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 }
加入了 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 }
輸出結果如圖
所以當元素很少的時候,或者說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 }
這樣也能避免指針丟失。