我最近修完了以下功能中的错误,答案让我感到惊讶。我有以下功能(在我发现错误之前写的):
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
vector<itemPtr>::iterator it; // itemPtr is a typedef for a std::tr1::shared_ptr<item::Item>
for(it=items.begin(); it!=items.end(); ++it)
{
if((*it)->getPosition() == pt)
{
item::Item item(**it);
items.erase(it);
vect.push_back(item);
}
}
}
此函数查找'items'向量中具有特定位置的所有Item
个对象,将它们从'items'中删除,并将它们放在'vect'中。之后,名为putItemsAt
的函数执行相反的操作,并将项添加到“项”。第一次,getItemsAt
工作正常。但是,在putItemsAt
被调用之后,getItemsAt
中的for循环将在'items'的末尾运行。 '它'将指向无效的Item
指针和getPosition()
段错误。在预感中,我将it!=items.end()
更改为it<items.end()
,并且有效。谁能告诉我为什么?环顾SO表明它可能涉及erase
使迭代器失效,但它仍然没有意义,为什么它会在第一次工作。
我也很好奇,因为我打算将'items'从向量更改为列表,因为list的擦除效率更高。我知道我必须使用!=
作为列表,因为它没有<
运算符。我会使用列表遇到同样的问题吗?
答案 0 :(得分:10)
当您调用erase()时,该迭代器将失效。因为那是你的循环迭代器,在使它失效后调用它上面的'++'运算符是未定义的行为。 erase()返回一个新的有效迭代器,指向向量中的下一个项目。你需要在循环中使用那个新的迭代器,即:
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
vector<itemPtr>::iterator it = items.begin();
while( it != items.end() )
{
if( (*it)->getPosition() == pt )
{
item::Item item(**it);
it = items.erase(it);
vect.push_back(item);
}
else
++it;
}
}
答案 1 :(得分:5)
您正在调用未定义的行为。由于您在该向量上调用了erase
,因此向量的所有迭代器都会失效。对于实现来说,做任何事情都是完全有效的。
当您致电items.erase(it);
时,it
现在无效。为了符合标准,您现在必须假设it
已经死亡。
在下次调用vect.push_back
时使用该无效迭代器调用未定义的行为。
使用it
作为for
循环的跟踪变量,再次调用未定义的行为。
您可以使用std::remove_copy_if
。
class ItemIsAtPoint : std::unary_function<bool, item::Item>
{
Point pt;
public:
ItemIsAtPoint(const Point& inPt) : pt(inPt) {}
bool operator()(const item::Item* input)
{
return input->GetPosition() == pt;
}
};
void Level::getItemsAt(vector<item::Item>& vect, const Point& pt)
{
std::size_t oldSize = items.size();
std::remove_copy_if(items.begin(), items.end(), std::back_inserter(vect),
ItemIsAtPoint(pt));
items.resize(vect.size() - (items.size() - oldSize));
}
如果你使用boost::bind
,你可以让它变得更漂亮,但这很有用。
答案 2 :(得分:2)
我将使用Remy Lebeau关于迭代器失效的解释,并且只需添加一个std::list
代替{std::vector
,可以使代码有效且渐近更快(线性时间,而不是二次时间) 1}}。 (std::list
删除仅使已删除的迭代器无效,插入不会使任何迭代器无效。)
通过激活STL实现的调试模式,您还可以在调试时预测识别迭代器失效。在GCC上,你使用编译器标志-D_GLIBCXX_DEBUG
(参见那里的一些警告)。