------------恢复内容开始------------
遇到的实际问题:
最近编程遇到了一个非常有趣的问题,编程题目里询问在 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 }
这样也能避免指针丢失。