近来,在阅读一份开源代码的时候,看到了类似如下的代码:
typedef std::map<int, std::string> id_names_t;

id_names_t id_names;
void EraseName(std::string name)
{
	id_names_t::iterator it = id_names.begin();
	while(it != id_names.end())
	{
		if(it->second == name)
		{
			id_names_t::iterator to_erase = it;
			++it;
			id_names.erase(to_erase);
			continue;
		}
		++it;
	}
}
 
 

上述代码乍看之下确实没有什么问题,但细究的话,似乎也不是甚妥当。在使用STL容器的迭代器时,需要考虑的一个问题就是迭代器是否会失效的问题,那么问题来了:

1. 在上述的代码中,id_names调用earse之后,失效的迭代器是否只有to_erase?

2. 在通常的使用中,或者说map的推荐使用中,为避免迭代器失效并且能够正常遍历,推荐使用方式为:id_names.erase(it++);这样的写法(当然在C++11中,由于map的erase接口的返回修改,也可以使用it=id_names.erase(it);),那么对于上述先直接++it;而后再调用id_names.erase(to_erase);这样的写法,会否存在潜在的隐患呢?

遇到上述问题,需要解决的办法,就是得了解map的实现。
map属于关联型容器,其使用的数据结构是红黑树(百度百科维基百科)。在树的实现实际都是采用指针的,如windows在实现的时候的部分代码段

if (_Where._Getcont() != this || this->_Isnil(_Where._Mynode()))
   _DEBUG_ERROR("map/set erase iterator outside range");
_Nodeptr _Erasednode = _Where._Mynode();	// node to erase
++_Where;	// save successor iterator for return
_Orphan_ptr(*this, _Erasednode);
 

因此:

1. 在erase的影响只会是被删除的当前的节点失效,不会造成其他的迭代器失效。

2. 上述代码片段中,id_names_t::iterator to_erase = it; ++it; id_names.erase(to_erase);的效果与id_names.erase(it++);等价。


对于迭代器的失效,不同的容器由于底层的数据结构不同,所引发的迭代器失效的情况也不尽相同,因此在使用的过程中需小心谨慎,以免出现迭代器已经失效却还在被使用的情况发生。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐