【问题标题】:std::next with n > std::distance(it, c.end())std::next 与 n > std::distance(it, c.end())
【发布时间】:2016-11-13 07:39:19
【问题描述】:

我不想使用std::distance,因为它会计算从我的迭代器到结尾的整个距离。但是我需要确保从迭代器到最后我有 N 个或更多元素。所以我正在使用下一个代码:

if (std::next(it, n) != c.end()) // c is a std::multimap
{
    /// my logic
}

一切都很好,可以使用我的编译器 (g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9)),但我有疑问。在文档(cpprefenece.com && cplusplus.com)中,我找不到有关n > std::distance(it , c.end()) 时的案例或任何其他例外情况的任何信息。所以。我的代码安全吗?或者我应该写我自己的nextIfPossible

【问题讨论】:

  • 我想说不要依赖std::next,尽管libstd++ 上*map 的实现目前可以根据您的需要工作。但在std::vector 的情况下,这同样行不通。由于跨容器的行为相同,因此您不应依赖它。
  • 只要你保证容器中至少有 n 元素,我觉得没问题。

标签: c++ c++11 stl


【解决方案1】:

根据标准§24.4.4/p3 & p6 迭代器操作 [iterator.operations](Emphasis Mine):

template <class InputIterator, class Distance>
constexpr void advance(InputIterator& i, Distance n);

2 要求:n 应为负数,仅适用于双向和随机 访问迭代器。

3效果:递增(或负 n 递减) 迭代器通过 n 引用 i。

template <class InputIterator>
constexpr InputIterator next(InputIterator x,
typename std::iterator_traits<InputIterator>::difference_type n = 1);

6效果:相当于:advance(x, n); return x;

因此,没有边界检查,因此如果输入 n 大于 std::distance(it , c.end()),您可能会导致未定义的行为。

【讨论】:

    【解决方案2】:

    如果distance(it, c.end()) 小于nnext(it, n) 是未定义的行为。

    [C++14: 5.7/5] 如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不会产生溢出;否则,行为未定义。

    查看这里了解更多信息:Are non dereferenced iterators past the "one past-the-end" iterator of an array undefined behavior?

    必须写一个nextIfPossible,否则你的代码是未定义的。也就是说,由于我猜这是一个随机访问迭代器,你会发现在必须执行边界检查的情况下,使用索引进行基准测试比使用迭代器更快:https://stackoverflow.com/a/37299761/2642059

    所以我建议不要使用迭代器或nextIfPossible,而只是使用索引并根据大小检查它。

    【讨论】:

    • 请注意,您在该帖子上接受了答案,它实际上是在谈论指针而不是迭代器。我仍然不确定我是否看到了证明 end() + 1 未定义的内容。
    • @NathanOliver 迭代器也是非法的,您可以在Ben Voigt's excellent answer 中找到我的第一个链接问题。但是这里复制的材料太多了。
    • 我仍然认为这不能证明什么。一方面,他说对于迭代器,一般不禁止递增超过“结束”(one-past-the-last-element),但对于大多数各种迭代器来说,它是被禁止的和对于另一件事,AFAIK 没有任何实际表明结束迭代器不可取消引用的内容。有一句引述标准从不假设它是,但恕我直言,这还不够。
    • @NathanOliver:在末尾递增是不安全的。对于某些迭代器(例如无限生成器),通过递增甚至不可能到达终点,这就是为什么没有普遍禁止的原因。对于其他可能到达末尾的情况,增量运算符的前提条件是迭代器当前位于末尾之前。
    • @Nathan UB 意味着实现可以分配有效的行为,但不是必须的,实现定义的意味着每个实现都必须接受它,但标准让实现可以选择行为。这是第一类——实现不需要提供任何关于在结束后取消引用迭代器的保证。
    【解决方案3】:

    libstd++ 中的 RB_Tree::iterator 增量运算符的实现确保它不会将迭代器返回到未定义的内存位置:

    static _Rb_tree_node_base*
      local_Rb_tree_increment(_Rb_tree_node_base* __x) throw ()
      {
        if (__x->_M_right != 0)
          {
            __x = __x->_M_right;
            while (__x->_M_left != 0)
              __x = __x->_M_left;
          }
        else
          {
            _Rb_tree_node_base* __y = __x->_M_parent;
            while (__x == __y->_M_right)
              {
                __x = __y;
                __y = __y->_M_parent;
              }
            if (__x->_M_right != __y)
              __x = __y;
          }
        return __x;
      }
    

    std::vector却不是这样:

    #include <iostream>
    #include <vector>
    #include <iterator>
    
    int main() {
      std::vector<int> vec = {1,2};
      auto it = vec.begin();
      it = std::next(it, 5);
      if (it != vec.end()) {
        std::cout << "Not end..go on" << std::endl;
      }
      return 0;
    }
    

    这将继续打印消息..

    因此,由于跨容器的行为不同,您不应该依赖 std::next 来获取基于 map 的容器的当前正确行为。

    【讨论】:

      猜你喜欢
      • 2015-04-19
      • 1970-01-01
      • 1970-01-01
      • 2019-09-10
      • 1970-01-01
      • 1970-01-01
      • 2013-06-30
      • 2013-02-07
      相关资源
      最近更新 更多