您的位置:首页 > 编程语言 > C语言/C++

C++ Runtime Error :STL list "list iterator not incrementable"

2015-11-22 13:23 585 查看

问题

最近在项目过程中用到了list这个容器,并且在使用过程中涉及了erase的删除元素操作,在程序的调试过程中经常会弹出异常对话框,提示的异常为:“list iterator not incrementable”,便上网查了一下问题的原因,网上的答案一致认为是遍历容器元素的时候使用erase删除元素,导致++iterator 的时候报了刚才的错误。但是我原来经常使用这类容器,对于erase的操作也不少,知道其中的陷阱在哪里,我确信我的代码中的没有常见的那种错误写法,于是我开始一句一句的调试代码,最后我发现还是我代码的问题,只不过代码隐藏的太隐秘了,一般不容易发现,下面我们先来看看一般的版本:

常见版本

#include "stdafx.h"
#include <iostream>
#include <list>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
list<int> listInt;
listInt.push_back(1);
listInt.push_back(2);
listInt.push_back(3);
for (list<int>::iterator iter = listInt.begin(); iter != listInt.end(); ++iter)
{
if (*iter == 2)
listInt.erase(iter);
}

return 0;
}


常见错误解决方法

以上的代码对于没有处理过此类问题的”程序猿“来说确实是个问题,erase中会将迭代器销毁掉,这样在erase之后,iter的指向的内容已经乱了,debug的时候可以看到开始是从1开始的,过后就是负多少了,再执行++iter就会报错list iterator not incrementable,简单的解决方法是在erase之后直接break出来就可以了,如果删除操作不至一次那可以利用erase的返回值,其返回值就指向下一个元素,代码如下 :

for (list<int>::iterator iter = listInt.begin(); iter != listInt.end();)
{
if (*iter == 2)
{
listInt.erase(iter);
}
else
{
++iter;
}
}


隐晦的错误

#include "stdafx.h"
#include <iostream>
#include <iterator>
#include <list>
using namespace std;

typedef void(*EVENT_CALLBACK)(const int);

struct EventPair
{
int             nEventId;
EVENT_CALLBACK  funcCallBack;

EventPair(const int nEvent, EVENT_CALLBACK callBack)
{
nEventId = nEvent;
funcCallBack = callBack;
}
};

class EventMgr
{
public:
EventMgr(){}
~EventMgr(){}

void RegisterEvent(const int nEvent, EVENT_CALLBACK callBack)
{
m_EventList.push_back(EventPair(nEvent, callBack));
}

void UnRegisterEvent(const int nEvent, EVENT_CALLBACK callBack)
{
for (list<EventPair>::iterator iter = m_EventList.begin(); iter != m_EventList.end();)
{
if ((*iter).nEventId == nEvent)
{
iter = m_EventList.erase(iter);
}
else
{
++iter;
}
}
}

void FilterEvent(const int nEvent, int nParam)
{
for (list<EventPair>::iterator iter = m_EventList.begin(); iter != m_EventList.end(); ++iter)
{
if ((*iter).nEventId == nEvent)
(*iter).funcCallBack(nParam);
}
}

private:
list<EventPair> m_EventList;
};

class Handle
{
private:
Handle(){}
~Handle(){}
public:
static Handle& Singleton();

static void doSomethig(int nParam)
{
// ...
}

static void doSomethig2(int nParam)
{
// ...
Handle::Singleton().GetEvent().UnRegisterEvent(2, Handle::doSomethig2);
}

static void doSomethig3(int nParam)
{
// ...
}

inline EventMgr& GetEvent()
{
return m_event;
}

private:
EventMgr m_event;
};

Handle& Handle::Singleton()
{
static Handle sIns;
return sIns;
}

int _tmain(int argc, _TCHAR* argv[])
{
// 事件注册
Handle::Singleton().GetEvent().RegisterEvent(1, Handle::doSomethig);
Handle::Singleton().GetEvent().RegisterEvent(2, Handle::doSomethig2);
Handle::Singleton().GetEvent().RegisterEvent(3, Handle::doSomethig3);

// 事件处理
Handle::Singleton().GetEvent().FilterEvent(2, 2);

return 0;
}


错误截图



错误分析及解决办法

代码有点长是不是,这还是精简版呢,凑合着看吧,不是这么长怎么错误那么难找呢,话说看了一遍代码有没有发现问题,不卖关子了,问题就出在FilterEvent这个函数上,一般的遍历是没有问题,可是这个函数在遍历的过程中调用了回调函数,回调函数是什么鬼,这个你得仔细查查了,本文中的回调函数就是doSomethig(),doSomethig2(),doSomethig3(),其中doSomethig2注销了事件,也就是FilterEvent遍历的过程中调用了doSomethig2(),而它其中包括erase,删除了list原有的元素,导致原有的iter失效,再调用++iter就报了上面的错误。

解决办法

防止在容器循环中处理容器的元素,特别是删除,可以复制一份来循环,修改的代码如下:

void FilterEvent(const int nEvent, int nParam)
{
list<EventPair> originalEventList;
std::copy(m_EventList.begin(), m_EventList.end(), std::back_inserter(originalEventList));

for (list<EventPair>::iterator iter = originalEventList.begin(); iter != originalEventList.end(); ++iter)
{
if ((*iter).nEventId == nEvent)
(*iter).funcCallBack(nParam);
}
}


总结

一定要避免在stl容器中处理迭代器,不可避免的时候查一下源代码,选择处理拷贝副本会比较安全。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息