STL容器的遍历删除 今天在对截包程序的HashTable中加入计时机制时,碰到这个问题。对hash_map中的每个项加入时间后,用查询函数遍历hash_map,以删除掉那些在表存留时间比某个阈值长的表项(当然这个函数是应该运行在另起一个线程上的),但是在按照下面的方法对hash_map(用迭代器)遍历删除时,当找到第一个满足删除条件的元素并将其删除后,程序将提示非法: for(list<int>::iterator iter = m_map.begin(); iter != m_map.end(); ++iter) //这种做法是错误的 { if(需要删除) { m_map.erase(iter); } } 原因是当前节点已经被删除后,其使用的内存空间被释放的,那么再对已经删除的节点获得位置信息,这种访问当然就是非法的。 正确的做法是: for(list<int>::iterator iter = m_map.begin(); iter != m_map.end(); ) //这是正确的做法 { if(需要删除) { m_map.erase(iter++); } else ++iter; } 为什么呢? map 是关联容器,由节点组成,每个节点都有自己独立的存储空间。除了包含元素对象之外,节点中还包含定位其前后相邻节点的位置信息(一般用指针表示),而且在 定位前、后节点(即对迭代器进行自减、自加运算)时都要根据当前节点中的位置信息进行计算。显然,如果此时当前节点已经被删除、其使用的内存空间被释放的 话,那么为了获得位置信息再对已经删除的节点进行访问就是非法的。 STL中对结点类容器(如list,hash_map)遍历时进行删除时,涉及到iterator的相关操作(以list为例): _Self& operator++() { this->_M_incr(); return *this; } _Self operator++(int) { _Self __tmp = *this; this->_M_incr(); return __tmp; //后缀++按照语意返回了++前的iterator, } void _M_incr() { _M_node = _M_node->_M_next; } //++的操作是使iterator的_M_node指向下一个结点 iterator erase(iterator __position) { _List_node_base* __next_node = __position._M_node->_M_next; _List_node_base* __prev_node = __position._M_node->_M_prev; _Node* __n = (_Node*) __position._M_node; __prev_node->_M_next = __next_node; __next_node->_M_prev = __prev_node; _STLP_STD::_Destroy(&__n->_M_data); //call T::~T() this->_M_node.deallocate(__n, 1); return iterator((_Node*)__next_node); } 分析代码我们可以看出,erase会释放掉__position的_M_node, 在__position上再进行++是错误的。 所以不能在m_map.erase(iter)后,进行iter++。 哪为什么m_map.erase(iter++)可以呢?为什么不能用m_map.erase(++iter)? 参照operator ++的代码我们可以找到答案。iter++返回了++之前的iter值,erase使用这个值能正确进行__position的前后结点的串接及删除正确 的结点,而++iter返回的是++之后的iter,所以m_map.erase(++iter)后串接不正确,iter->_M_node也是失 效的。 另外,对于非结点类,如数组类的容器vector,string,deque,如果erase会返回下个有效的iterator,可以这样处理: for(vector<int>::iterator iter = m_vector.begin(); iter != m_vector.end();) { if(需要删除) { iter=m_vector.erase(iter); } else ++iter; }