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 }

这样也能避免指针丢失。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM