【问题标题】:question about std::vector::end()关于 std::vector::end() 的问题
【发布时间】:2010-06-30 20:08:51
【问题描述】:

我最近修复了以下函数中的一个错误,答案让我感到惊讶。我有以下函数(在我发现错误之前写的):

    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' 的末尾运行。 'it' 将指向无效的Item 指针和getPosition() 段错误。凭直觉,我将it!=items.end() 更改为it&lt;items.end(),并且成功了。谁能告诉我为什么?环顾 SO 表明它可能涉及 erase 使迭代器无效,但它仍然没有意义为什么它会在第一次通过。

我也很好奇,因为我计划将“项目”从向量更改为列表,因为列表的擦除更有效。我知道我必须使用!= 作为列表,因为它没有&lt; 运算符。使用列表会遇到同样的问题吗?

【问题讨论】:

    标签: c++ stl stdvector


    【解决方案1】:

    当您调用 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:您发布的代码只是将 for 循环替换为 while 循环。它仍然无效。
    • @Billy:不完全是。他更正了擦除语句。语句“it = items.erase(it)”为其分配了一个新的有效值。
    • @Peter:vect.push_back 不会使其无效,因为 vect 是与 items 不同的向量。
    • @Ken:我误读了这个问题。我已经更正了我的评论。
    • @Max:请记住,Billy 的解决方案并不相同。 remove_copy_ifback_inserter 和他的 ItemIsAtPoint 谓词将推回 item::Item 对象到 vect 如果 getPosition() == pt。但是,复制到vect 的项目不会从items 中删除。
    【解决方案2】:

    您正在调用未定义的行为。由于您在该向量上调用了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,你可以让它更漂亮,但这很有效。

    【讨论】:

    • 在调试时,你应该使用你的 STL 实现的调试模式(STLPort 和 GNU libstdc++ 都有调试模式),它会将调试代码放在迭代器中,以引发大的危险信号(可能抛出异常)您尝试无效地使用已失效的迭代器。
    • 我想我没有在这里建立连接,但我需要从向量中删除元素的函数。就地修改元素如何帮助我做到这一点? @Ken Bloom:我正在用 g++ 编译并使用它的 '-g' 调试标志。我应该使用其他哪些?
    • @Max:您发布的代码不会从向量中删除元素。如果你想删除那些,你应该使用std::remove_ifstd::remove_copy_if 而不是手写循环。
    • @Billy:我的意思是他可以使他的代码渐近地比他已经拥有的代码更快。当我查看您的代码时,我注意到它与 Max 的代码所做的事情并不完全相同。我没有他的putItemsAt 代码,但我认为他的版本最终会在列表末尾包含所有修改过的项目,而你的最终会得到适当的修改。
    • 我不知道将remove_copy_if 的结果传递给eraseremove_copy_if 的结果是一个 OutputIterator -- 一个 back_inserter -- 而向量 erase 需要一个向量 iterator
    【解决方案3】:

    我将使用 Remy Lebeau 关于迭代器失效的解释,并补充一点,您可以通过使用 std::list 而不是 std::vector,使您的代码有效且渐近更快(线性时间,而不是二次时间)。 (std::list 删除只会使被删除的迭代器失效,而插入不会使任何迭代器失效。)

    您还可以在调试时通过激活 STL 实现的调试模式来预测地识别迭代器失效。在 GCC 上,您可以使用编译器标志 -D_GLIBCXX_DEBUG(请参阅那里的一些警告)。

    【讨论】:

      猜你喜欢
      • 2022-09-30
      • 1970-01-01
      • 1970-01-01
      • 2011-06-29
      • 2023-03-21
      • 1970-01-01
      • 2018-05-02
      • 2015-04-08
      • 1970-01-01
      相关资源
      最近更新 更多