【问题标题】:std::list sentinel node from standard point of view从标准的角度来看 std::list 哨兵节点
【发布时间】:2019-05-16 06:36:13
【问题描述】:

代码示例:


    list<int> mylist{10, 20, 30, 40};
    auto p = mylist.end();
    while (true)
    {
        p++;
        if (p == mylist.end()) // skip sentinel
            continue;
        cout << *p << endl;
    }

我想知道,从标准(C++17、n4810)的角度来看,这段代码在多大程度上是合法的? 我正在寻找与上述示例相关的双向迭代器要求,但没有运气。

我的问题是:

能够通过end(),是实现细节还是标准要求?

【问题讨论】:

  • 库从不假定过去的值是可取消引用的。我认为这不仅适用于end ,也适用于之后的情况。
  • 无论如何你都应该使用++p 而不是p++。预增量有正确的用法;你的不是其中之一。
  • 你的编译器(哪一个?)有一些调试模式(检查迭代器、消毒器等)吗?启用它会回答这个问题。

标签: c++ stl iterator language-lawyer


【解决方案1】:

引用最新的在线草稿。 [iterator.requirements.general]/7

正如指向数组的常规指针保证有一个指针值指向数组的最后一个元素,所以对于任何迭代器类型,都有一个迭代器值指向对应序列的最后一个元素。这些值称为过去值。定义了表达式*i 的迭代器i 的值称为可解引用。 库从不假定过去的值是可取消引用的。

我相信这不仅适用于end(),也适用于之后的情况。请注意,该标准并未明确规定永远不应取消引用 end()

并且Cpp17Iterator requirements table 声明对于表达式*rr 应该是可取消引用的:

past-the-end 迭代器被认为是不可递增的迭代器,并且递增它(就像您在 while 循环的开头所做的那样)会导致未定义的行为。

使用std::advance 时也可能发生类似您尝试做的事情。

Nicolai Josuttis 的书"The C++ Standard Library: A Tutorial and Reference" 有这样的引述:

请注意,advance() 不检查它是否与序列的end() 交叉(它无法检查,因为迭代器通常不知道它们操作的容器)。 因此,调用此函数可能会导致未定义的行为,因为未定义为序列结尾调用运算符 ++

【讨论】:

    【解决方案2】:

    您的代码是非法的。您首先将 p 初始化为 past-the-end 迭代器

    auto p = mylist.end();
    

    现在你p++。每Table 76r++ 的操作语义是:

    { X tmp = r;
    ++r;
    return tmp; }
    

    根据[Table 74]++r

    期望: r 是可取消引用的。

    根据[iterator.requirements.general]/7

    库从不假设过去的值是 可取消引用。

    换句话说,像你一样增加一个过去的迭代器是未定义的行为。

    【讨论】:

      猜你喜欢
      • 2014-10-11
      • 2021-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-27
      • 1970-01-01
      • 2022-11-27
      相关资源
      最近更新 更多