在一个向量上使用“遍历时删除”的模式之一,我不明白为什么这段代码能够工作,或者它是否利用了未定义的行为:
代码:
#include <vector>
#include <iostream>
int main(int argc, char* argv[], char* envz[])
{
std::vector<std::string> myVec;
myVec.push_back("1");
myVec.push_back("2");
myVec.push_back("3");
for (std::vector<std::string>::iterator i = myVec.begin();
i != myVec.end();
++i)
{
if ("1" == *i)
{
std::cout << "Erasing " << *i << std::endl;
i = myVec.erase(i);
--i;
continue;
}
std::cout << *i << std::endl;
}
return 0;
}
输出结果:
>g++ -g main.cpp
>./a.out
Erasing 1
2
3
问题:
考虑for循环的第一次迭代:
i
是 myVec.begin(),指向1
。- 我们进入条件块。
1
被删除,i
被设置为已删除元素后面的一个,也就是现在被 myVec.begin() 指向的2
- 我将
i
减少,现在它指向 ... myVec.begin() 之前的一个元素???
我对为什么这似乎有效产生了疑惑,正如输出所证明的那样,但是在迭代器减少时感觉不太对劲。如果条件是 if ("2" == *i)
,那么这段代码就很容易理解,因为迭代器减量仍然将其置于向量中的有效条目。换句话说,如果我们有条件地删除了 2
,则 i
将被设置为指向 3
,但然后手动减量并且指向 1
,随后进行 for 循环增量,使其再次指向 3
。有条件地删除最后一个元素也同样容易理解。
我尝试的其他方法:
这个观察让我假设在 vector::begin() 之前减量是幂等的,因此我尝试了额外的减量,如下所示:
#include <vector>
#include <iostream>
int main(int argc, char* argv[], char* envz[])
{
std::vector<std::string> myVec;
myVec.push_back("1");
myVec.push_back("2");
myVec.push_back("3");
for (std::vector<std::string>::iterator i = myVec.begin();
i != myVec.end();
++i)
{
if ("1" == *i)
{
std::cout << "Erasing " << *i << std::endl;
i = myVec.erase(i);
--i;
--i; /*** I thought this would be idempotent ***/
continue;
}
std::cout << *i << std::endl;
}
return 0;
}
但是这导致了段错误:
Erasing 1
Segmentation fault (core dumped)
有人可以解释一下为什么第一个代码块是有效的,特别是为什么在删除第一个元素后进行单个减少是有效的吗?
--i; ++i;
的结果是i
没有改变,这并不是完全令人惊讶的。 - Bo Persson--i
是幂等的呢?无论如何,如果你使用支持调试迭代器的实现(例如这个),并查看它以有用的错误消息中止,那么你将节省很多时间。 - Jonathan Wakely--i
指向vector::begin()
时,它是否是幂等的,或者它是否指向某个神奇的“pre-begin()”保留位置,从那里合法地增加回到begin()
。但这既不是这里也不是那里:这段代码似乎依赖于未定义的行为。 - StoneThrow