在使用vector的过程中,有时会遇到需要循环遍历vector,并删除符合指定条件的元素。
当“指定条件”不复杂时,应该尽量使用erase(remove_if(begin, end, func), end)的形式来完成功能。
但有时候“指定条件”过于复杂,不得不显式地写一个for循环来处理。我们必须小心在意erase所带来的side effect,一个一般性的for循环如下:
1 for (std::vector ::iterator it = intVec.begin(); it != intVec.end(); /**/) 2 { 3 if (*it == 3) 4 { 5 intVec.erase(it); 6 } 7 else 8 { 9 ++it;10 }11 }
所要注意的是it = intVec.erase(it)。实际上这里如果写成intVec.erase(it),即不对it做重新赋值,代码也能正常执行,特别是release版本几乎所有的编译器编译后都能产生结果正确的代码。而debug模式下有一些较新的编译器会在编译时给出警告,并在运行时出现断言错误。
为什么一个错误的写法在大多数情况下都能得到正确的答案?
根据STL的描述,执行erase(it)后,it和it之后的迭代器都可能会失效。这一点很好理解。因为vector一般由动态数组实现,它的元素在内存中是连续存储的。当删除掉it所指向元素时,原本在it后面的元素需要集体前移。迭代器本身几乎可以理解为是一个指针,在erase之后它所指向的位置并没有变化,只是那个位置的元素发生了变化,而且恰好变成了我们所想要的。至少大多数STL版本是这么实现的,因为这处理起来比较自然。
然而我们不能依赖于这个一般性事实,而应该采用it=intVec.erase(it)的形式来对it重新赋值。STL中有要求vector的erase函数要返回指向被erase的迭代器的下一个位置,写成it=intVec.erase(it)是万无一失的,而写成intVec.erase(it)虽然实际可行,但是具有潜在风险,万一某一天erase会影响it的指向(STL只要求erase移除元素,而没有保证it自身不变),程序就极有可能出问题。
根据标准所描述的约束来编程,而不是根据具体的实现细节来编程。
对于C++ STL,似乎有很多个版本的实现,而它们或多或少都有所偏差。这里有两个网站,可以进行参考:和。