【问题标题】:Does pop_back() really invalidate *all* iterators on an std::vector?pop_back() 是否真的使 std::vector 上的 *all* 迭代器无效?
【发布时间】:2010-09-08 21:45:42
【问题描述】:
std::vector<int> ints;

// ... fill ints with random values

for(std::vector<int>::iterator it = ints.begin(); it != ints.end(); )
{
    if(*it < 10)
    {
        *it = ints.back();
        ints.pop_back();
        continue;
    }
    it++;
}

此代码不起作用,因为当调用 pop_back() 时,it 无效。但我在std::vector::pop_back() 中没有找到任何关于迭代器失效的文档。

你有相关的链接吗?

【问题讨论】:

  • 您能否更详细地解释一下代码是如何失败的? pop_back 应该只使指向最后一个元素的迭代器失效,而不是全部。
  • 我真的很想知道代码应该做什么?目前的意图似乎是先遍历向量,如果找到不小于 10 的值,则删除向量的最后一个元素。顺便提一句。下一个迭代器应该是 ++it;
  • @PauldeVrieze:当不使用评估值时,任何明智的编译器都会使it++++it 完全相同。
  • @LightnessRacesinOrbit 在原始类型的情况下(它可能在水下)当然。但如果是运算符实现,则不能保证++itit++ 不完全不同。不是说有不同的意思就是好的风格,但语言并不能保证。
  • Iterator invalidation rules 的可能重复项

标签: c++ stl


【解决方案1】:

pop_back() 的调用删除了向量中的最后一个元素,因此该元素的迭代器无效。 pop_back() 调用确实使最后一个元素之前的项目的迭代器无效,只有重新分配才会这样做。来自 Josuttis 的“C++ 标准库参考”:

插入或删除元素 使引用、指针和 引用以下内容的迭代器 元素。如果插入导致 重新分配,它使所有 引用、迭代器和指针。

【讨论】:

  • 那么cpluplus网站上的文字是错的? cplusplus.com/reference/stl/vector/pop_back.html
  • 网站的语言是模棱两可的——它没有说它是否意味着所有迭代器都指向向量或元素。这意味着元素。
  • cplusplus.com 是一个糟糕的网站;切勿将其用作参考。
  • @SteveM 提出替代方案会很好。 =)
  • @BruceConnor http://www.cppreference.com 怎么样?
【解决方案2】:

pop_back() 仅使指向最后一个元素的迭代器无效。来自 C++ 标准库参考:

插入或删除元素 使引用、指针和 引用以下内容的迭代器 元素。如果插入导致 重新分配,它使所有 引用、迭代器和指针。

所以要回答你的问题,不,它不会使 所有 迭代器无效。

但是,在您的代码示例中,当它指向最后一个元素并且值低于 10 时,它可能会使 it 无效。在这种情况下,Visual Studio 调试 STL 会将迭代器标记为无效, 并进一步检查它是否不等于 end() 将显示一个断言。

如果迭代器被实现为纯指针(就像在所有非调试 STL 向量情况下一样),您的代码应该可以正常工作。如果迭代器不仅仅是指针,那么您的代码无法正确处理这种删除最后一个元素的情况。

【讨论】:

    【解决方案3】:

    您可能需要考虑使用erase 的返回值,而不是将后退元素交换到已删除位置并弹出。对于序列,擦除返回一个迭代器,该迭代器指向被删除元素之外的元素。请注意,此方法可能会导致比您的原始算法更多的复制。

    for(std::vector<int>::iterator it = ints.begin(); it != ints.end(); )
    {
        if(*it < 10)
            it = ints.erase( it );
        else
            ++it;
    }
    

    std::remove_if 也可以作为替代解决方案。

    struct LessThanTen { bool operator()( int n ) { return n < 10; } };
    
    ints.erase( std::remove_if( ints.begin(), ints.end(), LessThanTen() ), ints.end() );
    

    std::remove_if(就像我的第一个算法一样)稳定,所以它可能不是最有效的方法,但它很简洁。

    【讨论】:

      【解决方案4】:

      pop_back() 只会在 it 指向向量中的最后一项时使 it 无效。因此,只要向量中的最后一个 int 小于 10,您的代码就会失败,如下所示:

      *it = ints.back(); // 将 *it 设置为它已有的值
      ints.pop_back(); // 使迭代器无效
      继续; // 循环并访问无效的迭代器

      【讨论】:

        【解决方案5】:

        (我使用 C++0x 工作草案中使用的编号方案,obtainable here

        第 732 页的表 94 说 pop_back(如果它存在于序列容器中)具有以下效果:

        { iterator tmp = a.end(); 
        --tmp; 
        a.erase(tmp); } 
        

        23.1.1,第 12 点指出:

        除非另有说明(显式或根据其他函数定义一个函数),否则调用容器 成员函数或将容器作为参数传递给库函数不应使迭代器无效或更改 该容器内的对象的值。

        访问 end() 都作为应用前缀——没有这样的效果,但是擦除():

        23.2.6.4(关于vector.erase()第4点):

        效果:在擦除点或擦除点之后使迭代器和引用无效。

        因此,总而言之:pop_back() 只会根据标准使指向最后一个元素的迭代器无效。

        【讨论】:

          【解决方案6】:

          这是您的答案,直接来自《神圣标准》:

          23.2.4.2 载体满足容器和可逆容器(在 23.1 中的两个表格中给出)和序列的所有要求,包括大多数可选序列要求 (23.1.1)。
          23.1.1.12 表 68 表达式a.pop_back() 返回类型无效 操作语义a.erase(--a.end()) 容器向量、列表、双端队列

          注意 a.pop_back 等价于 a.erase(--a.end())。查看vector关于擦除的细节:

          23.2.4.3.3 - 迭代器擦除(迭代器位置) - 效果 - 使擦除点之后的所有迭代器和引用无效

          因此,一旦您调用 pop_back,任何指向先前最终元素(现在不再存在)的迭代器都将失效。

          查看您的代码,问题是当您删除最后一个元素并且列表变为空时,您仍然递增它并离开列表的末尾。

          【讨论】:

          • 查看您的代码,问题是当您删除最后一个元素并且列表变为空时,您仍然会增加它并离开列表的末尾。 -> 不,当你删除一个元素时,代码不会增加迭代器。
          【解决方案7】:

          “官方规范”是 C++ 标准。如果您没有 C++03 的副本,您可以从委员会的网站获取最新的 C++0x 草案:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2723.pdf

          容器要求的“操作语义”部分指定 pop_back() 等价于 { iterator i = end(); - 一世;擦除(一); }。擦除的 [vector.modifiers] 部分显示“效果:在擦除点或之后使迭代器和引用无效。”

          如果你想要直觉参数,pop_back 是 no-fail (因为在标准容器中破坏 value_types 是不允许抛出异常的),所以它不能做任何复制或分配(因为它们可以抛出),这意味着你可以猜到被擦除元素的迭代器和结束迭代器都失效了,但其余的都没有。

          【讨论】:

            【解决方案8】:

            这里引用了 SGI 的 STL 文档 (http://www.sgi.com/tech/stl/Vector.html):

            [5] 向量的迭代器在其内存被重新分配时失效。此外,在向量中间插入或删除元素会使指向插入或删除点之后元素的所有迭代器无效。因此,如果您使用 reserve() 预分配向量将使用的尽可能多的内存,并且所有插入和删除都在向量的末尾,则可以防止向量的迭代器失效。

            我认为 pop_back 只会使指向最后一个元素的迭代器和 end() 迭代器无效。我们确实需要查看代码失败的数据,以及它无法确定发生了什么的方式。据我所知,代码应该可以工作 - 此类代码中的常见问题是迭代器上的元素和 ++ 的删除发生在同一迭代中,@mikhaild 指出的方式。但是,在这段代码中,情况并非如此:调用 pop_back 时不会发生 it++。

            当它指向最后一个元素并且最后一个元素小于 10 时,仍然可能会发生一些不好的事情。我们现在比较一个 invalidated it 和 end()。它可能仍然有效,但无法保证。

            【讨论】:

            • 他正在使用 C++ 标准库。
            【解决方案9】:

            错误是当“it”指向vector的最后一个元素并且如果这个元素小于10,这个最后一个元素被删除。现在“it”指向 ints.end(),下一个“it++”将指针移动到 ints.end()+1,所以现在“it”从 ints.end() 中逃跑,你得到了无限循环扫描你所有的记忆:)。

            【讨论】:

            • no 下一个 it++ 因为它是 *continue 所以循环结束,仅此而已
            【解决方案10】:

            查看信息here (cplusplus.com)

            删除最后一个元素

            删除向量中的最后一个元素,有效地将向量大小减一并使所有迭代器和对其的引用无效。

            【讨论】:

            • 事实上,我想知道它是否是实现选择,或者是否有官方 STL 文档解释了这一点。
            • -1 用于引用 cplusplus.com。这也是模棱两可的。它的意思是“使所有迭代器和引用无效”,而不是向量。
            【解决方案11】:

            迭代器仅在重新分配存储时失效。 Google 是您的朋友:see footnote 5

            您的代码由于其他原因无法正常工作。

            【讨论】:

            • 在 Visual Studio 2008 上,在调试中,它会生成一个断言,告诉迭代器无效。所以,这绝对是问题所在。
            猜你喜欢
            • 2011-05-06
            • 1970-01-01
            • 2018-01-27
            • 1970-01-01
            • 2011-04-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多