关于错误map/set iterator not incrementable

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beyongwang/article/details/53074704

今天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操作后,迭代器会失效,此时需要重新初始化迭代器,保证后对于它的操作合法。

展开阅读全文

没有更多推荐了,返回首页