关于std :: vector :: end()的问题

时间:2010-06-30 20:08:52

标签: c++ stl stdvector

我最近修完了以下功能中的错误,答案让我感到惊讶。我有以下功能(在我发现错误之前写的):

    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的擦除效率更高。我知道我必须使用!=作为列表,因为它没有<运算符。我会使用列表遇到同样的问题吗?

3 个答案:

答案 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(参见那里的一些警告)。