今天Debug代码时,碰到一个关于迭代器的崩溃错误(仅限Debug模式,release会被容错):map/set iterator not incrementable,相关代码如下(仅演示思路代码):
//pretypedef
typedef std::multimap<int, int>::iterator multimapIterator;
typedef std::pair<multimapIterator, multimapIterator> milmapIteratorPair;
std::set<int> testSet;
std::multimap<int, int> muiMap;
//muiMap will be sorted automatically: { (1, 1), (1, 2), (2, 3), (3, 4) }
//even through with chaos insert order
muiMap.insert(std::pair<int, int>(1, 1));
muiMap.insert(std::pair<int, int>(2, 3));
muiMap.insert(std::pair<int, int>(1, 2));
muiMap.insert(std::pair<int, int>(2, 4));
for (multimapIterator pMultimapIt = muiMap.begin(); pMultimapIt != muiMap.end(); ++pMultimapIt)
{
unsigned int iCount = muiMap.count(pMultimapIt->first);
if (2 <= iCount)
{
milmapIteratorPair resultPair = muiMap.equal_range(pMultimapIt->first);
multimapIterator it = resultPair.first;
while (it != resultPair.second)
{
testSet.insert(it->second);
++it;
}
//erase all elements with key pMultimapIt->first, return erased count
muiMap.erase(pMultimapIt->first);
pMultimapIt = muiMap.begin();
}
}
代码初看之下没有问题,功能也都正常:将muiMap中key值出现次数等于或者多于两次的元素的值(value)存到testSet中,并从muiMap将这些元素删除。
但是,实际上该段代码存在两个问题:
1. 特定情况下会访问非法的iterator。
2. 遍历时,会有元素遗漏,尽管对功能没有影响。
我们一个一个来看。
1. 特定情况下会访问非法的iterator
这个问题多少跟for循环的执行顺序有关系,对于如下for循环:
for(A.initialization; B.condition; C.increment)
{
D...
}
其执行顺序为:
A->B->D->C->B->D->C->B->D…C->B->D,直到条件不满足。
而当muiMap中所有元素的出现次数都满足条件时,对于如下代码:
for (multimapIterator pMultimapIt = muiMap.begin(); pMultimapIt != muiMap.end(); ++pMultimapIt)
{
...
pMultimapIt = muiMap.begin();
...
}
此时muiMap为空,muiMap.begin()等于muiMap.end(),这样pMultimapIt也就指向了一个非法的iterator,所以下次for循环时,++pMultimapIt是对非法iterator自增操作,会产生错误,该错误的一个修改方法是添加muiMap判空操作,如果为空直接跳出循环,但这样解决不了第二个问题。而且在下面会看到,解决第二个问题的同时,这个问题也可以解决掉。
2. 遍历时,会有元素遗漏,尽管对功能没有影响
例如,对于如下multimap: { (1, 1), (1, 2), (2, 3), (3, 4) }
第一次循环时,key值1出现次数满足条件,所以删除之:
muiMap.erase(pMultimapIt->first);
multimap为:{ (2, 3), (3, 4) },之后执行:
pMultimapIt = muiMap.begin();
此时pMultimapIt指向pair: (2, 3)。然后for循环判断未到达容器末尾,因此执行++pMultimapIt,此时pMultimapIt指向(3, 4),对于(2, 3)的遍历被跳过了,之所以在此例子中不影响功能,是因为如果被跳过的元素的key值出现次数多于两次,跳过一个仍然可以被删掉,这得益与multimap自动排序,key相同的元素会被排到一起。所以,在某些情况下,pMultimapIt不应该自增。修改后的代码如下:
...
//no ++pMultimapIt anymore
for (multimapIterator pMultimapIt = muiMap.begin(); pMultimapIt != muiMap.end();) {
unsigned int iCount = muiMap.count(pMultimapIt->first);
if (2 <= iCount)
{
milmapIteratorPair resultPair = muiMap.equal_range(pMultimapIt->first);
multimapIterator it = resultPair.first;
while (it != resultPair.second)
{
testSet.insert(it->second);
++it;
}
muiMap.erase(pMultimapIt->first);
pMultimapIt = muiMap.begin();
}
else//increase pMultimapIt under conditions.
{
++pMultimapIt;
}
}
经过如上修改,由于删掉元素后,pMultimapIt会指向新的元素,素以此时pMultimapIt不需要增加,只有未删掉元素(iCount > 2)时,需要继续遍历下一个元素,所以自增pMultimapIt。
对于问题1,如果muiMap被删为空,则此时pMultimapIt等于muiMap.end(),跳出for循环,问题1解决。
总结
对于容器的迭代器(iterator)操作,一定要小心迭代器非法的情况,此时对于迭代器的任何操作,都会导致显性或者隐性的问题,尤其很多容器在erase操作后,迭代器会失效,此时需要重新初始化迭代器,保证后对于它的操作合法。