【问题标题】:What happens if you increment an iterator that is equal to the end iterator of an STL container如果增加一个等于 STL 容器的结束迭代器的迭代器会发生什么
【发布时间】:2010-11-06 15:53:20
【问题描述】:

如果当迭代器指向向量的最后一个元素时,我将迭代器增加 2 会怎样?在 this question 询问如何通过 2 个元素将迭代器调整为 STL 容器时,提供了两种不同的方法:

  • 使用算术运算符的一种形式 - +=2 或 ++ 两次
  • 或使用 std::advance()

当迭代器指向 STL 容器的最后一个元素或更远时,我已经使用 VC++ 7 测试了它们的边缘情况:

vector<int> vec;
vec.push_back( 1 );
vec.push_back( 2 );

vector<int>::iterator it = vec.begin();
advance( it, 2 );
bool isAtEnd = it == vec.end(); // true
it++; // or advance( it, 1 ); - doesn't matter
isAtEnd = it == vec.end(); //false
it = vec.begin();
advance( it, 3 );
isAtEnd = it == vec.end(); // false

我见过多次建议在遍历向量和其他容器时与 vector::end() 进行比较:

for( vector<int>::iterator it = vec.begin(); it != vec.end(); it++ ) {
    //manipulate the element through the iterator here
}

显然,如果迭代器超过循环内的最后一个元素,则 for 循环语句中的比较将评估为 false,并且循环将愉快地继续进行未定义的行为。

如果我曾经在迭代器上使用Advance() 或任何类型的增量操作并使其指向容器的末端,我是否正确,我将无法检测到这种情况?如果是这样,最佳做法是什么 - 不使用此类改进?

【问题讨论】:

    标签: c++ stl vector iterator


    【解决方案1】:

    以下是 Nicolai Josuttis 书中的引述:

    请注意,Advance() 不检查 是否越过 a 的 end() 序列(它无法检查,因为 迭代器一般不知道 它们操作的容器)。 因此,调用此函数可能 导致未定义的行为,因为 调用运算符 ++ 以结束 a 序列未定义

    换句话说,将迭代器维护在范围内的责任完全在于调用者。

    【讨论】:

    • 算术运算后如何判断是否在范围内?
    • @Narek 你不能。在可能出现 UB 的操作之后检查始终是 C++ 中的失败策略。
    【解决方案2】:

    也许你应该有这样的东西:

    template <typename Itr>
    Itr safe_advance(Itr i, Itr end, size_t delta)
    {
        while(i != end && delta--)
            i++;
        return i;
    }
    

    iterator_category&lt;Itr&gt;random_access_iterator 时,您可以重载它以执行以下操作:

    return (delta > end - i)? end : i + delta;
    

    【讨论】:

      【解决方案3】:

      您可以在迭代器 (it) 和 vec.begin() 处的迭代器之间使用“距离”函数,并将其与向量的大小(通过 size() 获得)进行比较。

      在这种情况下,您的 for 循环将如下所示:

      for (vector<int>::iterator it = vec.begin(); distance(vec.begin(), it) < vec.size(); ++it)
      {
           // Possibly advance n times here.
      }
      

      【讨论】:

      • 这依赖于连续的存储分配还是也适用于其他容器?
      • 这更多地与如何为每种类型的迭代器实现运算符++有关。 “距离”计算必须对一个迭代器执行多少 ++ 或 -- 才能到达另一个迭代器。如果我是你,我会在我期望的所有类型的容器上尝试它,看看距离能给我带来什么。
      • distance() 根据标准 (24.3.4) 作用于任何输入、前向、双向或随机访问迭代器
      • 距离针对随机访问迭代器进行了优化:它的复杂度为 O(1),因为它不计算 ++,但执行减法
      • @jalf "distance() 适用于任何输入迭代器":你可能不想这样做!
      【解决方案4】:

      Marijn 建议的代码有点错误(正如好奇的人指出的那样)。

      最后一行的正确版本是:

      bool isPastEnd = it >= vec.end();
      

      【讨论】:

      • it &gt; vec.end() 可以假定为始终为假。 ++vec.end() 是未定义的行为
      【解决方案5】:

      您还可以在 for 语句中进行更多比较:

      for( vector<int>::iterator it = vec.begin(); it != vec.end() && it+1 != vec.end(); it+=2 ) {
          //manipulate the element through the iterator here
      }
      

      我不知道这与 Kostas's 的建议相比会如何执行,但 感觉 小幅度的增量会更好。当然,由于您需要对每个增量进行检查,因此对于较大的增量将非常难以维护,但这是另一种选择。

      如果可能的话,我肯定会避免它。如果您确实需要一次增加 2 个值,请考虑使用 std::pair 向量或具有 2 个元素的结构向量。

      【讨论】:

        【解决方案6】:

        建议你看看Boost.Range
        使用起来可能更安全。
        它也将在 C++0x 中。

        【讨论】:

          【解决方案7】:

          container.end() -- 刚刚过去的元素 -- 是唯一定义的外部值。

          检查过的迭代器会在本质上是超出范围的访问时出错,但这并不是很有帮助(尤其是在默认行为是结束程序的情况下)。

          我认为最好的做法是“不要那样做”——要么检查迭代器的每个值(最好在作为过滤器包装的东西中),并且只对有趣的条目进行操作,或者明确地使用索引与

          for(int i = 0; i < vec.size(); i+=2) {...}
          

          【讨论】:

            【解决方案8】:

            即使这个问题已经有半年的历史了,提及使用比较运算符 > 和

            vector<int> vec;
            vec.push_back( 1 );
            vec.push_back( 2 );
            
            vector<int>::iterator it = vec.begin();
            
            it+=10; //equivalent to advance( it, 10 )
            bool isPastEnd = it > vec.end(); //true
            

            【讨论】:

            • 对不起,那是错误的。如前所述,一个有效的迭代器不能超过最后一个迭代器,即 end()。
            • 这只是因为 std::vector::iterator 的实现实际上是一个原始指针。如果使用另一个容器,情况可能会完全不同,当你尝试结束时,它甚至可能会抛出异常。
            猜你喜欢
            • 2013-02-21
            • 2018-07-14
            • 1970-01-01
            • 2019-04-19
            • 1970-01-01
            • 2011-08-22
            • 2014-12-05
            • 2013-04-16
            • 2011-12-25
            相关资源
            最近更新 更多